Compare commits

...

25 Commits

Author SHA1 Message Date
Charlie Marsh
a710e35ebc Bump version to 0.0.170 2022-12-08 11:36:24 -05:00
Charlie Marsh
49df43bb78 Use single newlines in .pyi import sorting (#1142) 2022-12-08 11:34:41 -05:00
Charlie Marsh
e338d9acbe Remove 'consider' language from check messages (#1135) 2022-12-07 20:10:36 -05:00
Charlie Marsh
5c8655f479 Bump ruff_macros to 0.0.169 2022-12-07 19:10:16 -05:00
Charlie Marsh
60987888a2 Re-increase max iterations to 100 2022-12-07 19:10:03 -05:00
Charlie Marsh
a81581c781 Bump ruff_macros to 0.0.168 2022-12-07 19:08:18 -05:00
Charlie Marsh
3152dd7a8e Don't prompt users to --fix if they ran with --fix (#1133) 2022-12-07 19:07:51 -05:00
Charlie Marsh
528416f07a Rename I252 to TID252; add redirects for all renamed codes (#1129) 2022-12-07 15:12:22 -05:00
Charlie Marsh
4405a6a903 Bump version to 0.0.168 2022-12-07 13:18:40 -05:00
Charlie Marsh
35fa2a3c32 Convert more BTree usages to Fx (#1112) 2022-12-07 12:21:12 -05:00
Charlie Marsh
bb67fbb73a Implement unused argument detection (ARG) (#1126)
Detect unused arguments
2022-12-07 12:15:41 -05:00
Charlie Marsh
d698c6123e Bump version to 0.0.167 2022-12-07 10:37:31 -05:00
Charlie Marsh
9579faffa8 Avoid flagging bare exception issues when exception is re-raised (#1124) 2022-12-07 10:37:08 -05:00
Phillip Verheyden
9c6e8c7644 Auto-generate the rules table of contents (#1121) 2022-12-07 10:03:42 -05:00
Charlie Marsh
7abecd4f0e Implement B905 (#1122) 2022-12-07 10:01:24 -05:00
Phillip Verheyden
b8ff209af8 Encode prefixes in README headings not just in TOC (#1109) 2022-12-07 09:24:49 -05:00
Jeong YunWon
c5451cd8ad Reduce indents (#1116) 2022-12-07 09:20:33 -05:00
Jonathan Plasse
92b9ab3010 Add aiter() and anext() to BUILTINS (#1118) 2022-12-07 09:20:06 -05:00
Edgar R. M
f2ac8c4ec2 Add flake8-import-conventions to TOC in readme (#1114) 2022-12-06 21:27:04 -05:00
Charlie Marsh
80e2f0c92e Bump version to 0.0.166 2022-12-06 16:06:19 -05:00
Edgar R. M
ea550abd3c Implement flake8-import-conventions (#1098) 2022-12-06 16:01:17 -05:00
Charlie Marsh
5c26777e4c Avoid flagging ANN errors in @overload implementations (#1110) 2022-12-06 12:46:38 -05:00
Laurent Baillet
6eb6b6eede Update readme in order to match pylint prefixes (#1105) 2022-12-06 08:59:45 -05:00
Charlie Marsh
f1d3e3698a Bump version to 0.0.165 2022-12-06 00:03:30 -05:00
Charlie Marsh
080411bc89 Re-create ruff snapshots 2022-12-06 00:03:14 -05:00
123 changed files with 3964 additions and 1280 deletions

View File

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

View File

@@ -105,6 +105,8 @@ You may also want to add the new configuration option to the `flake8-to-ruff` to
responsible for converting `flake8` configuration files to Ruff's TOML format. This logic
lives in `flake8_to_ruff/src/converter.rs`.
To update the documentation for supported configuration options, run `cargo dev generate-options`.
## Release process
As of now, Ruff has an ad hoc release process: releases are cut with high frequency via GitHub

8
Cargo.lock generated
View File

@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flake8-to-ruff"
version = "0.0.164-dev.0"
version = "0.0.170-dev.0"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1821,7 +1821,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.164"
version = "0.0.170"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1874,7 +1874,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.164"
version = "0.0.170"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1892,7 +1892,7 @@ dependencies = [
[[package]]
name = "ruff_macros"
version = "0.0.161"
version = "0.0.170"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -6,7 +6,7 @@ members = [
[package]
name = "ruff"
version = "0.0.164"
version = "0.0.170"
edition = "2021"
rust-version = "1.65.0"
@@ -41,7 +41,7 @@ quick-junit = { version = "0.3.2" }
rayon = { version = "1.5.3" }
regex = { version = "1.6.0" }
ropey = { version = "1.5.0", features = ["cr_lines", "simd"], default-features = false }
ruff_macros = { version = "0.0.161", path = "ruff_macros" }
ruff_macros = { version = "0.0.170", path = "ruff_macros" }
rustc-hash = { version = "1.1.0" }
rustpython-ast = { features = ["unparse"], git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }
rustpython-common = { git = "https://github.com/RustPython/RustPython.git", rev = "28f9f65ccc625f00835d84bbb5fba274dce5aa89" }

50
LICENSE
View File

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

191
README.md
View File

@@ -68,31 +68,33 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [Installation and Usage](#installation-and-usage)
1. [Configuration](#configuration)
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 (UP)](#pyupgrade)
1. [pep8-naming (N)](#pep8-naming)
1. [flake8-2020 (YTT)](#flake8-2020)
1. [flake8-annotations (ANN)](#flake8-annotations)
1. [flake8-bandit (S)](#flake8-bandit)
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-comprehensions (C4)](#flake8-comprehensions)
1. [flake8-debugger (T10)](#flake8-debugger)
1. [flake8-print (T20)](#flake8-print)
1. [flake8-quotes (Q)](#flake8-quotes)
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. [Supported Rules](#supported-rules) <!-- Begin auto-generated table of contents. -->
1. [Pyflakes (F)](#pyflakes-f)
1. [pycodestyle (E, W)](#pycodestyle-e-w)
1. [mccabe (C90)](#mccabe-c90)
1. [isort (I)](#isort-i)
1. [pydocstyle (D)](#pydocstyle-d)
1. [pyupgrade (UP)](#pyupgrade-up)
1. [pep8-naming (N)](#pep8-naming-n)
1. [flake8-2020 (YTT)](#flake8-2020-ytt)
1. [flake8-annotations (ANN)](#flake8-annotations-ann)
1. [flake8-bandit (S)](#flake8-bandit-s)
1. [flake8-blind-except (BLE)](#flake8-blind-except-ble)
1. [flake8-boolean-trap (FBT)](#flake8-boolean-trap-fbt)
1. [flake8-bugbear (B)](#flake8-bugbear-b)
1. [flake8-builtins (A)](#flake8-builtins-a)
1. [flake8-comprehensions (C4)](#flake8-comprehensions-c4)
1. [flake8-debugger (T10)](#flake8-debugger-t10)
1. [flake8-import-conventions (ICN)](#flake8-import-conventions-icn)
1. [flake8-print (T20)](#flake8-print-t20)
1. [flake8-quotes (Q)](#flake8-quotes-q)
1. [flake8-return (RET)](#flake8-return-ret)
1. [flake8-tidy-imports (TID)](#flake8-tidy-imports-tid)
1. [flake8-unused-arguments (ARG)](#flake8-unused-arguments-arg)
1. [eradicate (ERA)](#eradicate-era)
1. [pygrep-hooks (PGH)](#pygrep-hooks-pgh)
1. [Pylint (PLC, PLE, PLR, PLW)](#pylint-plc-ple-plr-plw)
1. [Ruff-specific rules (RUF)](#ruff-specific-rules-ruf)<!-- End auto-generated table of contents. -->
1. [Editor Integrations](#editor-integrations)
1. [FAQ](#faq)
1. [Development](#development)
@@ -145,7 +147,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.164
rev: v0.0.170
hooks:
- id: ruff
```
@@ -195,6 +197,13 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
# Assume Python 3.10.
target-version = "py310"
[tool.ruff.flake8-import-conventions.aliases]
altair = "alt"
"matplotlib.pyplot" = "plt"
numpy = "np"
pandas = "pd"
seaborn = "sns"
[tool.ruff.mccabe]
# Unlike Flake8, default to a complexity level of 10.
max-complexity = 10
@@ -387,8 +396,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
<!-- Sections automatically generated by `cargo dev generate-rules-table`. -->
<!-- Begin auto-generated sections. -->
### Pyflakes
### Pyflakes (F)
For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
@@ -437,7 +445,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
### pycodestyle
### pycodestyle (E, W)
For more, see [pycodestyle](https://pypi.org/project/pycodestyle/2.9.1/) on PyPI.
@@ -460,7 +468,7 @@ 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
### mccabe (C90)
For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
@@ -468,7 +476,7 @@ For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
| ---- | ---- | ------- | --- |
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |
### isort
### isort (I)
For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
@@ -476,7 +484,7 @@ For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
| ---- | ---- | ------- | --- |
| I001 | UnsortedImports | Import block is un-sorted or un-formatted | 🛠 |
### pydocstyle
### pydocstyle (D)
For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
@@ -527,7 +535,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D418 | SkipDocstring | Function decorated with `@overload` shouldn't contain a docstring | |
| D419 | NonEmpty | Docstring is empty | |
### pyupgrade
### pyupgrade (UP)
For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
@@ -548,7 +556,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
| UP014 | ConvertNamedTupleFunctionalToClass | Convert `...` from `NamedTuple` functional to class syntax | 🛠 |
| UP015 | RedundantOpenModes | Unnecessary open mode parameters | 🛠 |
### pep8-naming
### pep8-naming (N)
For more, see [pep8-naming](https://pypi.org/project/pep8-naming/0.13.2/) on PyPI.
@@ -570,7 +578,7 @@ 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 | |
### flake8-2020
### flake8-2020 (YTT)
For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI.
@@ -587,7 +595,7 @@ For more, see [flake8-2020](https://pypi.org/project/flake8-2020/1.7.0/) on PyPI
| 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
### flake8-annotations (ANN)
For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2.9.1/) on PyPI.
@@ -605,7 +613,7 @@ For more, see [flake8-annotations](https://pypi.org/project/flake8-annotations/2
| ANN206 | MissingReturnTypeClassMethod | Missing return type annotation for classmethod `...` | |
| ANN401 | DynamicallyTypedExpression | Dynamically typed expressions (typing.Any) are disallowed in `...` | |
### flake8-bandit
### flake8-bandit (S)
For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on PyPI.
@@ -618,15 +626,15 @@ 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-blind-except
### flake8-blind-except (BLE)
For more, see [flake8-blind-except](https://pypi.org/project/flake8-blind-except/0.2.1/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| BLE001 | BlindExcept | Blind except Exception: statement | |
| BLE001 | BlindExcept | Do not catch blind exception: `Exception` | |
### flake8-boolean-trap
### flake8-boolean-trap (FBT)
For more, see [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap/0.1.0/) on PyPI.
@@ -636,7 +644,7 @@ For more, see [flake8-boolean-trap](https://pypi.org/project/flake8-boolean-trap
| FBT002 | BooleanDefaultValueInFunctionDefinition | Boolean default value in function definition | |
| FBT003 | BooleanPositionalValueInFunctionCall | Boolean positional value in function call | |
### flake8-bugbear
### flake8-bugbear (B)
For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/) on PyPI.
@@ -669,8 +677,9 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged | |
| B027 | EmptyMethodWithoutAbstractDecorator | `...` is an empty method in an abstract base class, but has no abstract decorator | |
| B904 | RaiseWithoutFromInsideExcept | Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling | |
| B905 | ZipWithoutExplicitStrict | `zip()` without an explicit `strict=` parameter | |
### flake8-builtins
### flake8-builtins (A)
For more, see [flake8-builtins](https://pypi.org/project/flake8-builtins/2.0.1/) on PyPI.
@@ -680,7 +689,7 @@ 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-comprehensions
### flake8-comprehensions (C4)
For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehensions/3.10.1/) on PyPI.
@@ -703,7 +712,7 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens
| C416 | UnnecessaryComprehension | Unnecessary `(list\|set)` comprehension (rewrite using `(list\|set)()`) | 🛠 |
| C417 | UnnecessaryMap | Unnecessary `map` usage (rewrite using a `(list\|set\|dict)` comprehension) | |
### flake8-debugger
### flake8-debugger (T10)
For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/) on PyPI.
@@ -711,7 +720,13 @@ For more, see [flake8-debugger](https://pypi.org/project/flake8-debugger/4.1.2/)
| ---- | ---- | ------- | --- |
| T100 | Debugger | Import for `...` found | |
### flake8-print
### flake8-import-conventions (ICN)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ICN001 | ImportAliasIsNotConventional | `...` should be imported as `...` | |
### flake8-print (T20)
For more, see [flake8-print](https://pypi.org/project/flake8-print/5.0.0/) on PyPI.
@@ -720,7 +735,7 @@ For more, see [flake8-print](https://pypi.org/project/flake8-print/5.0.0/) on Py
| T201 | PrintFound | `print` found | 🛠 |
| T203 | PPrintFound | `pprint` found | 🛠 |
### flake8-quotes
### flake8-quotes (Q)
For more, see [flake8-quotes](https://pypi.org/project/flake8-quotes/3.3.1/) on PyPI.
@@ -731,7 +746,7 @@ 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-return
### flake8-return (RET)
For more, see [flake8-return](https://pypi.org/project/flake8-return/1.2.0/) on PyPI.
@@ -746,15 +761,27 @@ For more, see [flake8-return](https://pypi.org/project/flake8-return/1.2.0/) on
| RET507 | SuperfluousElseContinue | Unnecessary `else` after `continue` statement | |
| RET508 | SuperfluousElseBreak | Unnecessary `else` after `break` statement | |
### flake8-tidy-imports
### flake8-tidy-imports (TID)
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| I252 | BannedRelativeImport | Relative imports are banned | |
| TID252 | BannedRelativeImport | Relative imports are banned | |
### eradicate
### flake8-unused-arguments (ARG)
For more, see [flake8-unused-arguments](https://pypi.org/project/flake8-unused-arguments/0.0.12/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| ARG001 | UnusedFunctionArgument | Unused function argument: `...` | |
| ARG002 | UnusedMethodArgument | Unused method argument: `...` | |
| ARG003 | UnusedClassMethodArgument | Unused class method argument: `...` | |
| ARG004 | UnusedStaticMethodArgument | Unused static method argument: `...` | |
| ARG005 | UnusedLambdaArgument | Unused lambda argument: `...` | |
### eradicate (ERA)
For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
@@ -762,7 +789,7 @@ For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
| ---- | ---- | ------- | --- |
| ERA001 | CommentedOutCode | Found commented-out code | 🛠 |
### pygrep-hooks
### pygrep-hooks (PGH)
For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitHub.
@@ -770,7 +797,7 @@ For more, see [pygrep-hooks](https://github.com/pre-commit/pygrep-hooks) on GitH
| ---- | ---- | ------- | --- |
| PGH001 | NoEval | No builtin `eval()` allowed | |
### Pylint
### Pylint (PLC, PLE, PLR, PLW)
For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
@@ -781,12 +808,12 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| 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()` | 🛠 |
| PLR0402 | ConsiderUsingFromImport | Use `from ... import ...` in lieu of alias | |
| PLR1701 | ConsiderMergingIsinstance | Merge these isinstance calls: `isinstance(..., (...))` | |
| PLR1722 | UseSysExit | Use `sys.exit()` instead of `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
### Ruff-specific rules (RUF)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
@@ -940,6 +967,7 @@ natively, including:
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
@@ -952,6 +980,12 @@ natively, including:
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
originating Flake8 plugins. For example, Ruff uses `TID252` to represent the `I252` rule from
`flake8-tidy-imports`. This helps minimize conflicts across plugins and allows any individual plugin
to be toggled on or off with a single (e.g.) `--select TID`, as opposed to `--select I2` (to avoid
conflicts with the `isort` rules, like `I001`).
Beyond the rule set, Ruff suffers from the following limitations vis-à-vis Flake8:
1. Ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
@@ -988,6 +1022,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
- [`flake8-debugger`](https://pypi.org/project/flake8-debugger/)
- [`flake8-docstrings`](https://pypi.org/project/flake8-docstrings/)
- [`flake8-eradicate`](https://pypi.org/project/flake8-eradicate/)
- [`flake8-import-conventions`](https://github.com/joaopalmeiro/flake8-import-conventions)
- [`flake8-print`](https://pypi.org/project/flake8-print/)
- [`flake8-quotes`](https://pypi.org/project/flake8-quotes/)
- [`flake8-return`](https://pypi.org/project/flake8-return/)
@@ -1449,7 +1484,7 @@ fix = true
A list of check code prefixes to consider autofix-able.
**Default value**: `["A", "ANN", "B", "BLE", "C", "D", "E", "F", "FBT", "I", "M", "N", "Q", "RUF", "S", "T", "U", "W", "YTT"]`
**Default value**: `["A", "ANN", "ARG", "B", "BLE", "C", "D", "E", "ERA", "F", "FBT", "I", "ICN", "N", "PGH", "PLC", "PLE", "PLR", "PLW", "Q", "RET", "RUF", "S", "T", "TID", "UP", "W", "YTT"]`
**Type**: `Vec<CheckCodePrefix>`
@@ -1758,6 +1793,48 @@ extend-immutable-calls = ["fastapi.Depends", "fastapi.Query"]
---
### `flake8-import-conventions`
#### [`aliases`](#aliases)
The conventional aliases for imports. These aliases can be extended by the `extend_aliases` option.
**Default value**: `{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}`
**Type**: `FxHashMap<String, String>`
**Example usage**:
```toml
[tool.ruff.flake8-import-conventions]
# Declare the default aliases.
altair = "alt"
matplotlib.pyplot = "plt"
numpy = "np"
pandas = "pd"
seaborn = "sns"
```
---
#### [`extend-aliases`](#extend-aliases)
A mapping of modules to their conventional import aliases. These aliases will be added to the `aliases` mapping.
**Default value**: `{}`
**Type**: `FxHashMap<String, String>`
**Example usage**:
```toml
[tool.ruff.flake8-import-conventions]
# Declare a custom alias for the `matplotlib` module.
"dask.dataframe" = "dd"
```
---
### `flake8-quotes`
#### [`avoid-escape`](#avoid-escape)

View File

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

View File

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

View File

@@ -270,6 +270,7 @@ mod tests {
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -314,6 +315,7 @@ mod tests {
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -358,6 +360,7 @@ mod tests {
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -402,6 +405,7 @@ mod tests {
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -451,6 +455,7 @@ mod tests {
avoid_escape: None,
}),
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -533,6 +538,7 @@ mod tests {
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -583,6 +589,7 @@ mod tests {
avoid_escape: None,
}),
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,

View File

@@ -0,0 +1,49 @@
from typing import overload
@overload
def foo(i: int) -> "int":
...
@overload
def foo(i: "str") -> "str":
...
def foo(i):
return i
@overload
def bar(i: int) -> "int":
...
@overload
def bar(i: "str") -> "str":
...
class X:
def bar(i):
return i
# TODO(charlie): This third case should raise an error (as in Mypy), because we have a
# statement between the interfaces and implementation.
@overload
def baz(i: int) -> "int":
...
@overload
def baz(i: "str") -> "str":
...
x = 1
def baz(i):
return i

View File

@@ -53,3 +53,11 @@ try:
raise e
except Exception:
pass
try:
pass
except Exception as e:
raise bad
except BaseException:
pass

View File

@@ -0,0 +1,10 @@
zip()
zip(range(3))
zip("a", "b")
zip("a", "b", *zip("c"))
zip(zip("a"), strict=False)
zip(zip("a", strict=True))
zip(range(3), strict=True)
zip("a", "b", strict=False)
zip("a", "b", "c", strict=True)

View File

@@ -0,0 +1,25 @@
import math # not checked
import altair # unconventional
import dask.array # unconventional
import dask.dataframe # unconventional
import matplotlib.pyplot # unconventional
import numpy # unconventional
import pandas # unconventional
import seaborn # unconventional
import altair as altr # unconventional
import matplotlib.pyplot as plot # unconventional
import dask.array as darray # unconventional
import dask.dataframe as ddf # unconventional
import numpy as nmp # unconventional
import pandas as pdas # unconventional
import seaborn as sbrn # unconventional
import altair as alt # conventional
import dask.array as da # conventional
import dask.dataframe as dd # conventional
import matplotlib.pyplot as plt # conventional
import numpy as np # conventional
import pandas as pd # conventional
import seaborn as sns # conventional

View File

@@ -0,0 +1,19 @@
import math # not checked
import altair # unconventional
import matplotlib.pyplot # unconventional
import numpy # unconventional
import pandas # unconventional
import seaborn # unconventional
import altair as altr # unconventional
import matplotlib.pyplot as plot # unconventional
import numpy as nmp # unconventional
import pandas as pdas # unconventional
import seaborn as sbrn # unconventional
import altair as alt # conventional
import matplotlib.pyplot as plt # conventional
import numpy as np # conventional
import pandas as pd # conventional
import seaborn as sns # conventional

View File

@@ -0,0 +1,19 @@
import math # not checked
import altair # unconventional
import matplotlib.pyplot # unconventional
import numpy # unconventional
import pandas # unconventional
import seaborn # unconventional
import altair as altr # unconventional
import matplotlib.pyplot as plot # unconventional
import numpy as np # unconventional
import pandas as pdas # unconventional
import seaborn as sbrn # unconventional
import altair as alt # conventional
import matplotlib.pyplot as plt # conventional
import numpy as nmp # conventional
import pandas as pd # conventional
import seaborn as sns # conventional

View File

@@ -0,0 +1,19 @@
import math # not checked
import altair # unconventional
import matplotlib.pyplot # unconventional
import numpy # not checked
import pandas # unconventional
import seaborn # unconventional
import altair as altr # unconventional
import matplotlib.pyplot as plot # unconventional
import numpy as nmp # not checked
import pandas as pdas # unconventional
import seaborn as sbrn # unconventional
import altair as alt # conventional
import matplotlib.pyplot as plt # conventional
import numpy as np # not checked
import pandas as pd # conventional
import seaborn as sns # conventional

View File

@@ -0,0 +1,137 @@
from abc import abstractmethod
from typing_extensions import override
###
# Unused arguments on functions.
###
def f(self, x):
print("Hello, world!")
def f(cls, x):
print("Hello, world!")
def f(self, x):
...
def f(cls, x):
...
###
# Unused arguments on lambdas.
###
lambda x: print("Hello, world!")
class X:
###
# Unused arguments.
###
def f(self, x):
print("Hello, world!")
def f(self, /, x):
print("Hello, world!")
def f(cls, x):
print("Hello, world!")
@classmethod
def f(cls, x):
print("Hello, world!")
@staticmethod
def f(cls, x):
print("Hello, world!")
@staticmethod
def f(x):
print("Hello, world!")
###
# Unused arguments attached to empty functions (OK).
###
def f(self, x):
...
def f(self, /, x):
...
def f(cls, x):
...
@classmethod
def f(cls, x):
...
@staticmethod
def f(cls, x):
...
@staticmethod
def f(x):
...
###
# Unused functions attached to abstract methods (OK).
###
@abstractmethod
def f(self, x):
print("Hello, world!")
@abstractmethod
def f(self, /, x):
print("Hello, world!")
@abstractmethod
def f(cls, x):
print("Hello, world!")
@classmethod
@abstractmethod
def f(cls, x):
print("Hello, world!")
@staticmethod
@abstractmethod
def f(cls, x):
print("Hello, world!")
@staticmethod
@abstractmethod
def f(x):
print("Hello, world!")
###
# Unused functions attached to overrides (OK).
###
@override
def f(self, x):
print("Hello, world!")
@override
def f(self, /, x):
print("Hello, world!")
@override
def f(cls, x):
print("Hello, world!")
@classmethod
@override
def f(cls, x):
print("Hello, world!")
@staticmethod
@override
def f(cls, x):
print("Hello, world!")
@staticmethod
@override
def f(x):
print("Hello, world!")

View File

@@ -0,0 +1,41 @@
import a
import b
x = 1
import os
import sys
def f():
pass
if True:
x = 1
import collections
import typing
class X: pass
y = 1
import os
import sys
"""Docstring"""
if True:
import os
def f():
pass
if True:
import os
def f():
pass
if True:
x = 1
import collections
import typing
class X: pass
if True:
x = 1
import collections
import typing
def f(): pass

View File

@@ -41,3 +41,9 @@ staticmethod-decorators = ["staticmethod"]
[tool.ruff.flake8-tidy-imports]
ban-relative-imports = "parents"
[tool.ruff.flake8-import-conventions.aliases]
pandas = "pd"
[tool.ruff.flake8-import-conventions.extend-aliases]
"dask.dataframe" = "dd"

View File

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

View File

@@ -8,7 +8,7 @@ use anyhow::Result;
use clap::Parser;
use codegen::{Scope, Type, Variant};
use itertools::Itertools;
use ruff::checks::{CheckCode, REDIRECTS};
use ruff::checks::{CheckCode, CODE_REDIRECTS, PREFIX_REDIRECTS};
use strum::IntoEnumIterator;
const FILE: &str = "src/checks_gen.rs";
@@ -39,34 +39,26 @@ pub fn main(cli: &Cli) -> Result<()> {
}
}
// 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(),
);
}
}
// Add any prefix aliases (e.g., "U" to "UP").
for (alias, source) in PREFIX_REDIRECTS.iter() {
prefix_to_codes.insert(
(*alias).to_string(),
prefix_to_codes
.get(&(*source).to_string())
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
.clone(),
);
}
// Add any check code aliases (e.g., "U001" to "UP001").
for (alias, check_code) in CODE_REDIRECTS.iter() {
prefix_to_codes.insert(
(*alias).to_string(),
prefix_to_codes
.get(&check_code.as_ref().to_string())
.unwrap_or_else(|| panic!("Unknown CheckCode: {alias:?}"))
.clone(),
);
}
let mut scope = Scope::new();
@@ -76,6 +68,7 @@ pub fn main(cli: &Cli) -> Result<()> {
.new_enum("CheckCodePrefix")
.vis("pub")
.derive("EnumString")
.derive("AsRefStr")
.derive("Debug")
.derive("PartialEq")
.derive("Eq")
@@ -112,10 +105,10 @@ pub fn main(cli: &Cli) -> Result<()> {
.line("#[allow(clippy::match_same_arms)]")
.line("match self {");
for (prefix, codes) in &prefix_to_codes {
if let Some(target) = REDIRECTS.get(&prefix.as_str()) {
if let Some(target) = CODE_REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been renamed to \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
target.as_ref(),
@@ -124,6 +117,18 @@ pub fn main(cli: &Cli) -> Result<()> {
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
} else if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
target,
codes
.iter()
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
} else {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => vec![{}],",
@@ -170,9 +175,9 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push('\n');
output.push_str("use colored::Colorize;");
output.push('\n');
output.push_str("use serde::{{Serialize, Deserialize}};");
output.push_str("use serde::{Deserialize, Serialize};");
output.push('\n');
output.push_str("use strum_macros::EnumString;");
output.push_str("use strum_macros::{AsRefStr, EnumString};");
output.push('\n');
output.push('\n');
output.push_str("use crate::checks::CheckCode;");
@@ -186,7 +191,9 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push_str("pub const CATEGORIES: &[CheckCodePrefix] = &[");
output.push('\n');
for prefix in prefix_to_codes.keys() {
if prefix.chars().all(char::is_alphabetic) {
if prefix.chars().all(char::is_alphabetic)
&& !PREFIX_REDIRECTS.contains_key(&prefix.as_str())
{
output.push_str(&format!("CheckCodePrefix::{prefix},"));
output.push('\n');
}

View File

@@ -7,11 +7,15 @@ use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use itertools::Itertools;
use ruff::checks::{CheckCategory, CheckCode};
use strum::IntoEnumIterator;
const BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
const END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
const TABLE_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated sections. -->";
const TABLE_END_PRAGMA: &str = "<!-- End auto-generated sections. -->";
const TOC_BEGIN_PRAGMA: &str = "<!-- Begin auto-generated table of contents. -->";
const TOC_END_PRAGMA: &str = "<!-- End auto-generated table of contents. -->";
#[derive(Args)]
pub struct Cli {
@@ -22,73 +26,91 @@ pub struct Cli {
pub fn main(cli: &Cli) -> Result<()> {
// Generate the table string.
let mut output = String::new();
let mut table_out = String::new();
let mut toc_out = String::new();
for check_category in CheckCategory::iter() {
output.push_str(&format!("### {}", check_category.title()));
output.push('\n');
output.push('\n');
let codes_csv: String = check_category.codes().iter().map(AsRef::as_ref).join(", ");
table_out.push_str(&format!("### {} ({codes_csv})", check_category.title()));
table_out.push('\n');
table_out.push('\n');
toc_out.push_str(&format!(
" 1. [{} ({})](#{}-{})\n",
check_category.title(),
codes_csv,
check_category.title().to_lowercase().replace(' ', "-"),
codes_csv.to_lowercase().replace(',', "-").replace(' ', "")
));
if let Some((url, platform)) = check_category.url() {
output.push_str(&format!(
table_out.push_str(&format!(
"For more, see [{}]({}) on {}.",
check_category.title(),
url,
platform
));
output.push('\n');
output.push('\n');
table_out.push('\n');
table_out.push('\n');
}
output.push_str("| Code | Name | Message | Fix |");
output.push('\n');
output.push_str("| ---- | ---- | ------- | --- |");
output.push('\n');
table_out.push_str("| Code | Name | Message | Fix |");
table_out.push('\n');
table_out.push_str("| ---- | ---- | ------- | --- |");
table_out.push('\n');
for check_code in CheckCode::iter() {
if check_code.category() == check_category {
let check_kind = check_code.kind();
let fix_token = if check_kind.fixable() { "🛠" } else { "" };
output.push_str(&format!(
table_out.push_str(&format!(
"| {} | {} | {} | {} |",
check_kind.code().as_ref(),
check_kind.as_ref(),
check_kind.summary().replace('|', r"\|"),
fix_token
));
output.push('\n');
table_out.push('\n');
}
}
output.push('\n');
table_out.push('\n');
}
if cli.dry_run {
print!("{output}");
print!("Table of Contents: {toc_out}\n Rules Tables: {table_out}");
} else {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.
let index = existing
.find(BEGIN_PRAGMA)
.expect("Unable to find begin pragma");
let prefix = &existing[..index + BEGIN_PRAGMA.len()];
// Extract the suffix.
let index = existing
.find(END_PRAGMA)
.expect("Unable to find end pragma");
let suffix = &existing[index..];
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
write!(f, "{prefix}\n\n")?;
write!(f, "{output}")?;
write!(f, "{suffix}")?;
// Extra newline in the markdown numbered list looks weird
replace_readme_section(toc_out.trim_end(), TOC_BEGIN_PRAGMA, TOC_END_PRAGMA)?;
replace_readme_section(&table_out, TABLE_BEGIN_PRAGMA, TABLE_END_PRAGMA)?;
}
Ok(())
}
fn replace_readme_section(content: &str, begin_pragma: &str, end_pragma: &str) -> Result<()> {
// Read the existing file.
let file = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("Failed to find root directory")
.join("README.md");
let existing = fs::read_to_string(&file)?;
// Extract the prefix.
let index = existing
.find(begin_pragma)
.expect("Unable to find begin pragma");
let prefix = &existing[..index + begin_pragma.len()];
// Extract the suffix.
let index = existing
.find(end_pragma)
.expect("Unable to find end pragma");
let suffix = &existing[index..];
// Write the prefix, new contents, and suffix.
let mut f = OpenOptions::new().write(true).truncate(true).open(&file)?;
writeln!(f, "{prefix}")?;
write!(f, "{content}")?;
write!(f, "{suffix}")?;
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_macros"
version = "0.0.161"
version = "0.0.170"
edition = "2021"
[lib]

9
src/ast/cast.rs Normal file
View File

@@ -0,0 +1,9 @@
use rustpython_ast::{Expr, Stmt, StmtKind};
pub fn decorator_list(stmt: &Stmt) -> &Vec<Expr> {
match &stmt.node {
StmtKind::FunctionDef { decorator_list, .. }
| StmtKind::AsyncFunctionDef { decorator_list, .. } => decorator_list,
_ => panic!("Expected StmtKind::FunctionDef | StmtKind::AsyncFunctionDef"),
}
}

65
src/ast/function_type.rs Normal file
View File

@@ -0,0 +1,65 @@
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::Expr;
use crate::ast::helpers::{
collect_call_paths, dealias_call_path, match_call_path, to_module_and_member,
};
use crate::ast::types::{Scope, ScopeKind};
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")];
pub enum FunctionType {
Function,
Method,
ClassMethod,
StaticMethod,
}
/// Classify a function based on its scope, name, and decorators.
pub fn classify(
scope: &Scope,
name: &str,
decorator_list: &[Expr],
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
import_aliases: &FxHashMap<&str, &str>,
classmethod_decorators: &[String],
staticmethod_decorators: &[String],
) -> FunctionType {
let ScopeKind::Class(scope) = &scope.kind else {
return FunctionType::Function;
};
// Special-case class method, like `__new__`.
if CLASS_METHODS.contains(&name)
|| scope.bases.iter().any(|expr| {
// The class itself extends a known metaclass, so all methods are class methods.
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
METACLASS_BASES
.iter()
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
})
|| decorator_list.iter().any(|expr| {
// The method is decorated with a class method decorator (like `@classmethod`).
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
classmethod_decorators.iter().any(|decorator| {
let (module, member) = to_module_and_member(decorator);
match_call_path(&call_path, module, member, from_imports)
})
})
{
FunctionType::ClassMethod
} else if decorator_list.iter().any(|expr| {
// The method is decorated with a static method decorator (like
// `@staticmethod`).
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
staticmethod_decorators.iter().any(|decorator| {
let (module, member) = to_module_and_member(decorator);
match_call_path(&call_path, module, member, from_imports)
})
}) {
FunctionType::StaticMethod
} else {
// It's an instance method.
FunctionType::Method
}
}

View File

@@ -1,3 +1,5 @@
pub mod cast;
pub mod function_type;
pub mod helpers;
pub mod operations;
pub mod relocate;

View File

@@ -1,7 +1,7 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use rustc_hash::FxHashMap;
use rustpython_ast::{Expr, Keyword, Stmt};
use rustpython_ast::{Arguments, Expr, Keyword, Stmt};
use rustpython_parser::ast::{Located, Location};
fn id() -> usize {
@@ -30,35 +30,49 @@ impl Range {
}
}
#[derive(Clone, Debug)]
pub struct FunctionScope {
#[derive(Debug)]
pub struct FunctionDef<'a> {
pub name: &'a str,
pub args: &'a Arguments,
pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// pub returns: Option<&'a Expr>,
// pub type_comment: Option<&'a str>,
// TODO(charlie): Create AsyncFunctionDef to mirror the AST.
pub async_: bool,
pub uses_locals: bool,
}
#[derive(Clone, Debug)]
pub struct ClassScope<'a> {
#[derive(Debug)]
pub struct ClassDef<'a> {
pub name: &'a str,
pub bases: &'a [Expr],
pub keywords: &'a [Keyword],
// pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
}
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct Lambda<'a> {
pub args: &'a Arguments,
pub body: &'a Expr,
}
#[derive(Debug)]
pub enum ScopeKind<'a> {
Class(ClassScope<'a>),
Function(FunctionScope),
Class(ClassDef<'a>),
Function(FunctionDef<'a>),
Generator,
Module,
Arg,
Lambda,
Lambda(Lambda<'a>),
}
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct Scope<'a> {
pub id: usize,
pub kind: ScopeKind<'a>,
pub import_starred: bool,
pub uses_locals: bool,
pub values: FxHashMap<&'a str, Binding>,
}
@@ -68,6 +82,7 @@ impl<'a> Scope<'a> {
id: id(),
kind,
import_starred: false,
uses_locals: false,
values: FxHashMap::default(),
}
}

View File

@@ -139,20 +139,21 @@ pub fn get(
return None;
};
if let Ok(encoded) = read_sync(cache_key(path, settings, autofix)) {
match bincode::deserialize::<CheckResult>(&encoded[..]) {
Ok(CheckResult {
metadata: CacheMetadata { mtime },
messages,
}) => {
if FileTime::from_last_modification_time(metadata).unix_seconds() == mtime {
return Some(messages);
}
}
Err(e) => error!("Failed to deserialize encoded cache entry: {e:?}"),
let encoded = read_sync(cache_key(path, settings, autofix)).ok()?;
let (mtime, messages) = match bincode::deserialize::<CheckResult>(&encoded[..]) {
Ok(CheckResult {
metadata: CacheMetadata { mtime },
messages,
}) => (mtime, messages),
Err(e) => {
error!("Failed to deserialize encoded cache entry: {e:?}");
return None;
}
};
if FileTime::from_last_modification_time(metadata).unix_seconds() != mtime {
return None;
}
None
Some(messages)
}
/// Set a value in the cache.

View File

@@ -17,7 +17,8 @@ use crate::ast::helpers::{
use crate::ast::operations::extract_all_names;
use crate::ast::relocate::relocate_expr;
use crate::ast::types::{
Binding, BindingContext, BindingKind, ClassScope, FunctionScope, Node, Range, Scope, ScopeKind,
Binding, BindingContext, BindingKind, ClassDef, FunctionDef, Lambda, Node, Range, Scope,
ScopeKind,
};
use crate::ast::visitor::{walk_excepthandler, Visitor};
use crate::ast::{helpers, operations, visitor};
@@ -35,8 +36,9 @@ 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_return, flake8_tidy_imports, mccabe, pep8_naming, pycodestyle, pydocstyle,
pyflakes, pygrep_hooks, pylint, pyupgrade,
flake8_import_conventions, flake8_print, flake8_return, flake8_tidy_imports,
flake8_unused_arguments, mccabe, pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks,
pylint, pyupgrade,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -71,7 +73,7 @@ pub struct Checker<'a> {
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>,
deferred_assignments: Vec<(usize, DeferralContext)>,
// Internal, derivative state.
visible_scope: VisibleScope,
in_f_string: Option<Range>,
@@ -142,15 +144,16 @@ impl<'a> Checker<'a> {
// If we're in an f-string, override the location. RustPython doesn't produce
// reliable locations for expressions within f-strings, so we use the
// span of the f-string itself as a best-effort default.
if let Some(range) = self.in_f_string {
self.checks.push(Check {
let check = if let Some(range) = self.in_f_string {
Check {
location: range.location,
end_location: range.end_location,
..check
});
}
} else {
self.checks.push(check);
}
check
};
self.checks.push(check);
}
/// Add multiple `Check` items to the `Checker`.
@@ -579,7 +582,7 @@ where
for expr in decorator_list {
self.visit_expr(expr);
}
self.push_scope(Scope::new(ScopeKind::Class(ClassScope {
self.push_scope(Scope::new(ScopeKind::Class(ClassDef {
name,
bases,
keywords,
@@ -670,7 +673,7 @@ where
pylint::plugins::useless_import_alias(self, alias);
}
if self.settings.enabled.contains(&CheckCode::PLR0402) {
pylint::plugins::consider_using_from_import(self, alias);
pylint::plugins::use_from_import(self, alias);
}
if let Some(asname) = &alias.node.asname {
@@ -727,6 +730,19 @@ where
}
}
}
if self.settings.enabled.contains(&CheckCode::ICN001) {
if let Some(check) =
flake8_import_conventions::checks::check_conventional_import(
stmt,
&alias.node.name,
alias.node.asname.as_deref(),
&self.settings.flake8_import_conventions.aliases,
)
{
self.add_check(check);
}
}
}
}
StmtKind::ImportFrom {
@@ -888,7 +904,7 @@ where
);
}
if self.settings.enabled.contains(&CheckCode::I252) {
if self.settings.enabled.contains(&CheckCode::TID252) {
if let Some(check) = flake8_tidy_imports::checks::banned_relative_import(
stmt,
level.as_ref(),
@@ -1063,9 +1079,6 @@ where
if self.settings.enabled.contains(&CheckCode::B013) {
flake8_bugbear::plugins::redundant_tuple_in_exception_handler(self, handlers);
}
if self.settings.enabled.contains(&CheckCode::BLE001) {
flake8_blind_except::plugins::blind_except(self, handlers);
}
}
StmtKind::Assign { targets, value, .. } => {
if self.settings.enabled.contains(&CheckCode::E731) {
@@ -1395,20 +1408,18 @@ where
}
Ok(summary) => {
if self.settings.enabled.contains(&CheckCode::F522) {
if let Some(check) =
pyflakes::checks::string_dot_format_extra_named_arguments(
&summary, keywords, location,
)
if let Some(check) = pyflakes::checks::string_dot_format_extra_named_arguments(
&summary, keywords, location,
)
{
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::F523) {
if let Some(check) =
pyflakes::checks::string_dot_format_extra_positional_arguments(
&summary, args, location,
)
if let Some(check) = pyflakes::checks::string_dot_format_extra_positional_arguments(
&summary, args, location,
)
{
self.add_check(check);
}
@@ -1444,16 +1455,15 @@ where
if self.settings.enabled.contains(&CheckCode::UP005) {
pyupgrade::plugins::deprecated_unittest_alias(self, func);
}
if self.settings.enabled.contains(&CheckCode::UP012) {
pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords);
}
// flake8-super
if self.settings.enabled.contains(&CheckCode::UP008) {
pyupgrade::plugins::super_call_with_parameters(self, expr, func, args);
}
if self.settings.enabled.contains(&CheckCode::UP012) {
pyupgrade::plugins::unnecessary_encode_utf8(self, expr, func, args, keywords);
}
// flake8-print
if self.settings.enabled.contains(&CheckCode::T201)
|| self.settings.enabled.contains(&CheckCode::T203)
@@ -1461,6 +1471,7 @@ where
flake8_print::plugins::print_call(self, expr, func);
}
// flake8-bugbear
if self.settings.enabled.contains(&CheckCode::B004) {
flake8_bugbear::plugins::unreliable_callable_check(self, expr, func, args);
}
@@ -1475,7 +1486,7 @@ where
.scope_stack
.iter()
.rev()
.any(|index| matches!(self.scopes[*index].kind, ScopeKind::Lambda))
.any(|index| matches!(self.scopes[*index].kind, ScopeKind::Lambda(..)))
{
flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args);
}
@@ -1488,6 +1499,15 @@ where
self, args, keywords,
);
}
if self.settings.enabled.contains(&CheckCode::B905)
&& self.settings.target_version >= PythonVersion::Py310
{
flake8_bugbear::plugins::zip_without_explicit_strict(
self, expr, func, keywords,
);
}
// flake8-bandit
if self.settings.enabled.contains(&CheckCode::S102) {
if let Some(check) = flake8_bandit::plugins::exec_used(expr, func) {
self.add_check(check);
@@ -1727,9 +1747,7 @@ where
if id == "locals" && matches!(ctx, ExprContext::Load) {
let scope = &mut self.scopes
[*(self.scope_stack.last().expect("No current scope found"))];
if let ScopeKind::Function(inner) = &mut scope.kind {
inner.uses_locals = true;
}
scope.uses_locals = true;
}
}
@@ -1755,7 +1773,7 @@ where
pylint::plugins::unnecessary_direct_lambda_call(self, expr, func);
}
if self.settings.enabled.contains(&CheckCode::PLR1722) {
pylint::plugins::consider_using_sys_exit(self, func);
pylint::plugins::use_sys_exit(self, func);
}
}
ExprKind::Dict { keys, .. } => {
@@ -2050,7 +2068,7 @@ where
}
}
}
ExprKind::Lambda { args, .. } => {
ExprKind::Lambda { args, body, .. } => {
// Visit the arguments, but avoid the body, which will be deferred.
for arg in &args.posonlyargs {
if let Some(expr) = &arg.node.annotation {
@@ -2083,7 +2101,7 @@ where
for expr in &args.defaults {
self.visit_expr(expr);
}
self.push_scope(Scope::new(ScopeKind::Lambda));
self.push_scope(Scope::new(ScopeKind::Lambda(Lambda { args, body })));
}
ExprKind::ListComp { elt, generators } | ExprKind::SetComp { elt, generators } => {
if self.settings.enabled.contains(&CheckCode::C416) {
@@ -2111,7 +2129,7 @@ where
}
ExprKind::BoolOp { op, values } => {
if self.settings.enabled.contains(&CheckCode::PLR1701) {
pylint::plugins::consider_merging_isinstance(self, expr, op, values);
pylint::plugins::merge_isinstance(self, expr, op, values);
}
}
_ => {}
@@ -2324,16 +2342,25 @@ where
ExcepthandlerKind::ExceptHandler {
type_, name, body, ..
} => {
if self.settings.enabled.contains(&CheckCode::E722) && type_.is_none() {
self.add_check(Check::new(
CheckKind::DoNotUseBareExcept,
if self.settings.enabled.contains(&CheckCode::E722) {
if let Some(check) = pycodestyle::checks::do_not_use_bare_except(
type_.as_deref(),
body,
Range::from_located(excepthandler),
));
) {
self.add_check(check);
}
}
if self.settings.enabled.contains(&CheckCode::B904) {
{
flake8_bugbear::plugins::raise_without_from_inside_except(self, body);
}
flake8_bugbear::plugins::raise_without_from_inside_except(self, body);
}
if self.settings.enabled.contains(&CheckCode::BLE001) {
flake8_blind_except::plugins::blind_except(
self,
type_.as_deref(),
name.as_ref().map(String::as_str),
body,
);
}
match name {
Some(name) => {
@@ -2929,27 +2956,44 @@ impl<'a> Checker<'a> {
fn check_deferred_functions(&mut self) {
while let Some((stmt, (scopes, parents), visibility)) = self.deferred_functions.pop() {
self.scope_stack = scopes;
self.parent_stack = parents;
self.scope_stack = scopes.clone();
self.parent_stack = parents.clone();
self.visible_scope = visibility;
self.push_scope(Scope::new(ScopeKind::Function(FunctionScope {
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
uses_locals: false,
})));
match &stmt.node {
StmtKind::FunctionDef { body, args, .. }
| StmtKind::AsyncFunctionDef { body, args, .. } => {
StmtKind::FunctionDef {
name,
body,
args,
decorator_list,
..
}
| StmtKind::AsyncFunctionDef {
name,
body,
args,
decorator_list,
..
} => {
self.push_scope(Scope::new(ScopeKind::Function(FunctionDef {
name,
body,
args,
decorator_list,
async_: matches!(stmt.node, StmtKind::AsyncFunctionDef { .. }),
})));
self.visit_arguments(args);
for stmt in body {
self.visit_stmt(stmt);
}
}
_ => {}
_ => unreachable!("Expected StmtKind::FunctionDef | StmtKind::AsyncFunctionDef"),
}
self.deferred_assignments
.push(*self.scope_stack.last().expect("No current scope found"));
self.deferred_assignments.push((
*self.scope_stack.last().expect("No current scope found"),
(scopes, parents),
));
self.pop_scope();
}
@@ -2957,25 +3001,29 @@ impl<'a> Checker<'a> {
fn check_deferred_lambdas(&mut self) {
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));
self.scope_stack = scopes.clone();
self.parent_stack = parents.clone();
if let ExprKind::Lambda { args, body } = &expr.node {
self.push_scope(Scope::new(ScopeKind::Lambda(Lambda { args, body })));
self.visit_arguments(args);
self.visit_expr(body);
} else {
unreachable!("Expected ExprKind::Lambda");
}
self.deferred_assignments
.push(*self.scope_stack.last().expect("No current scope found"));
self.deferred_assignments.push((
*self.scope_stack.last().expect("No current scope found"),
(scopes, parents),
));
self.pop_scope();
}
}
fn check_deferred_assignments(&mut self) {
if self.settings.enabled.contains(&CheckCode::F841) {
while let Some(index) = self.deferred_assignments.pop() {
while let Some((index, (scopes, _parents))) = self.deferred_assignments.pop() {
if self.settings.enabled.contains(&CheckCode::F841) {
self.add_checks(
pyflakes::checks::unused_variables(
&self.scopes[index],
@@ -2984,6 +3032,23 @@ impl<'a> Checker<'a> {
.into_iter(),
);
}
if self.settings.enabled.contains(&CheckCode::ARG001)
|| self.settings.enabled.contains(&CheckCode::ARG002)
|| self.settings.enabled.contains(&CheckCode::ARG003)
|| self.settings.enabled.contains(&CheckCode::ARG004)
|| self.settings.enabled.contains(&CheckCode::ARG005)
{
self.add_checks(
flake8_unused_arguments::plugins::unused_arguments(
self,
&self.scopes[*scopes
.last()
.expect("Expected parent scope above function scope")],
&self.scopes[index],
)
.into_iter(),
);
}
}
}
@@ -3063,7 +3128,7 @@ impl<'a> Checker<'a> {
for (name, binding) in &scope.values {
let (BindingKind::Importation(_, full_name, context)
| BindingKind::SubmoduleImportation(_, full_name, context)
| BindingKind::FromImportation(_, full_name, context)) = &binding.kind else { continue };
| BindingKind::FromImportation(_, full_name, context)) = &binding.kind else { continue; };
// Skip used exports from `__all__`
if binding.used.is_some()
@@ -3132,6 +3197,8 @@ impl<'a> Checker<'a> {
}
fn check_definitions(&mut self) {
let mut overloaded_name: Option<String> = None;
self.definitions.reverse();
while let Some((definition, visibility)) = self.definitions.pop() {
// flake8-annotations
if self.settings.enabled.contains(&CheckCode::ANN001)
@@ -3146,7 +3213,21 @@ impl<'a> Checker<'a> {
|| self.settings.enabled.contains(&CheckCode::ANN206)
|| self.settings.enabled.contains(&CheckCode::ANN401)
{
flake8_annotations::plugins::definition(self, &definition, &visibility);
// TODO(charlie): This should be even stricter, in that an overload
// implementation should come immediately after the overloaded
// interfaces, without any AST nodes in between. Right now, we
// only error when traversing definition boundaries (functions,
// classes, etc.).
if !overloaded_name.map_or(false, |overloaded_name| {
flake8_annotations::helpers::is_overload_impl(
self,
&definition,
&overloaded_name,
)
}) {
flake8_annotations::plugins::definition(self, &definition, &visibility);
}
overloaded_name = flake8_annotations::helpers::overloaded_name(self, &definition);
}
// pydocstyle

View File

@@ -1,5 +1,7 @@
//! Lint rules based on import analysis.
use std::path::Path;
use rustpython_parser::ast::Suite;
use crate::ast::visitor::Visitor;
@@ -33,8 +35,9 @@ pub fn check_imports(
directives: &IsortDirectives,
settings: &Settings,
autofix: bool,
path: &Path,
) -> Vec<Check> {
let mut tracker = ImportTracker::new(directives);
let mut tracker = ImportTracker::new(directives, path);
for stmt in python_ast {
tracker.visit_stmt(stmt);
}

View File

@@ -7,7 +7,7 @@ use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind, REDIRECTS};
use crate::checks::{Check, CheckCode, CheckKind, CODE_REDIRECTS};
use crate::noqa;
use crate::noqa::{is_file_exempt, Directive};
use crate::settings::Settings;
@@ -20,19 +20,18 @@ static URL_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^https?://\S+$").unwra
/// Whether the given line is too long and should be reported.
fn should_enforce_line_length(line: &str, length: usize, limit: usize) -> bool {
if length > limit {
let mut chunks = line.split_whitespace();
if let (Some(first), Some(_)) = (chunks.next(), chunks.next()) {
// Do not enforce the line length for commented lines that end with a URL
// or contain only a single word.
!(first == "#" && chunks.last().map_or(true, |c| URL_REGEX.is_match(c)))
} else {
// Single word / no printable chars - no way to make the line shorter
false
}
} else {
false
if length <= limit {
return false;
}
let mut chunks = line.split_whitespace();
let (Some(first), Some(_)) = (chunks.next(), chunks.next()) else {
// Single word / no printable chars - no way to make the line shorter
return false;
};
// Do not enforce the line length for commented lines that end with a URL
// or contain only a single word.
!(first == "#" && chunks.last().map_or(true, |c| URL_REGEX.is_match(c)))
}
pub fn check_lines(
@@ -210,7 +209,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);
let code = 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 {

View File

@@ -10,6 +10,7 @@ use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks_gen::CheckCodePrefix;
use crate::flake8_debugger::types::DebuggerUsingType;
use crate::flake8_quotes::settings::Quote;
use crate::flake8_tidy_imports::settings::Strictness;
@@ -134,6 +135,7 @@ pub enum CheckCode {
B026,
B027,
B904,
B905,
// flake8-blind-except
BLE001,
// flake8-comprehensions
@@ -158,7 +160,7 @@ pub enum CheckCode {
// mccabe
C901,
// flake8-tidy-imports
I252,
TID252,
// flake8-return
RET501,
RET502,
@@ -290,6 +292,14 @@ pub enum CheckCode {
FBT001,
FBT002,
FBT003,
// flake8-unused-arguments
ARG001,
ARG002,
ARG003,
ARG004,
ARG005,
// flake8-import-conventions
ICN001,
// Ruff
RUF001,
RUF002,
@@ -317,10 +327,12 @@ pub enum CheckCategory {
Flake8Builtins,
Flake8Comprehensions,
Flake8Debugger,
Flake8ImportConventions,
Flake8Print,
Flake8Quotes,
Flake8Return,
Flake8TidyImports,
Flake8UnusedArguments,
Eradicate,
PygrepHooks,
Pylint,
@@ -354,10 +366,12 @@ impl CheckCategory {
CheckCategory::Flake8Builtins => "flake8-builtins",
CheckCategory::Flake8Comprehensions => "flake8-comprehensions",
CheckCategory::Flake8Debugger => "flake8-debugger",
CheckCategory::Flake8ImportConventions => "flake8-import-conventions",
CheckCategory::Flake8Print => "flake8-print",
CheckCategory::Flake8Quotes => "flake8-quotes",
CheckCategory::Flake8Return => "flake8-return",
CheckCategory::Flake8TidyImports => "flake8-tidy-imports",
CheckCategory::Flake8UnusedArguments => "flake8-unused-arguments",
CheckCategory::Isort => "isort",
CheckCategory::McCabe => "mccabe",
CheckCategory::PEP8Naming => "pep8-naming",
@@ -371,6 +385,42 @@ impl CheckCategory {
}
}
pub fn codes(&self) -> Vec<CheckCodePrefix> {
match self {
CheckCategory::Eradicate => vec![CheckCodePrefix::ERA],
CheckCategory::Flake82020 => vec![CheckCodePrefix::YTT],
CheckCategory::Flake8Annotations => vec![CheckCodePrefix::ANN],
CheckCategory::Flake8Bandit => vec![CheckCodePrefix::S],
CheckCategory::Flake8BlindExcept => vec![CheckCodePrefix::BLE],
CheckCategory::Flake8BooleanTrap => vec![CheckCodePrefix::FBT],
CheckCategory::Flake8Bugbear => vec![CheckCodePrefix::B],
CheckCategory::Flake8Builtins => vec![CheckCodePrefix::A],
CheckCategory::Flake8Comprehensions => vec![CheckCodePrefix::C4],
CheckCategory::Flake8Debugger => vec![CheckCodePrefix::T10],
CheckCategory::Flake8Print => vec![CheckCodePrefix::T20],
CheckCategory::Flake8Quotes => vec![CheckCodePrefix::Q],
CheckCategory::Flake8Return => vec![CheckCodePrefix::RET],
CheckCategory::Flake8TidyImports => vec![CheckCodePrefix::TID],
CheckCategory::Flake8UnusedArguments => vec![CheckCodePrefix::ARG],
CheckCategory::Isort => vec![CheckCodePrefix::I],
CheckCategory::McCabe => vec![CheckCodePrefix::C90],
CheckCategory::PEP8Naming => vec![CheckCodePrefix::N],
CheckCategory::Pycodestyle => vec![CheckCodePrefix::E, CheckCodePrefix::W],
CheckCategory::Pydocstyle => vec![CheckCodePrefix::D],
CheckCategory::Pyflakes => vec![CheckCodePrefix::F],
CheckCategory::PygrepHooks => vec![CheckCodePrefix::PGH],
CheckCategory::Pylint => vec![
CheckCodePrefix::PLC,
CheckCodePrefix::PLE,
CheckCodePrefix::PLR,
CheckCodePrefix::PLW,
],
CheckCategory::Pyupgrade => vec![CheckCodePrefix::UP],
CheckCategory::Flake8ImportConventions => vec![CheckCodePrefix::ICN],
CheckCategory::Ruff => vec![CheckCodePrefix::RUF],
}
}
pub fn url(&self) -> Option<(&'static str, &'static Platform)> {
match self {
CheckCategory::Eradicate => {
@@ -412,6 +462,7 @@ impl CheckCategory {
"https://pypi.org/project/flake8-debugger/4.1.2/",
&Platform::PyPI,
)),
CheckCategory::Flake8ImportConventions => None,
CheckCategory::Flake8Print => Some((
"https://pypi.org/project/flake8-print/5.0.0/",
&Platform::PyPI,
@@ -428,6 +479,10 @@ impl CheckCategory {
"https://pypi.org/project/flake8-tidy-imports/4.8.0/",
&Platform::PyPI,
)),
CheckCategory::Flake8UnusedArguments => Some((
"https://pypi.org/project/flake8-unused-arguments/0.0.12/",
&Platform::PyPI,
)),
CheckCategory::Isort => {
Some(("https://pypi.org/project/isort/5.10.1/", &Platform::PyPI))
}
@@ -583,13 +638,13 @@ pub enum CheckKind {
ConsiderUsingFromImport(String, String),
AwaitOutsideAsync,
UselessElseOnLoop,
ConsiderUsingSysExit,
UseSysExit(String),
// flake8-builtins
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
// flake8-blind-except
BlindExcept,
BlindExcept(String),
// flake8-bugbear
AbstractBaseClassWithoutAbstractMethod(String),
AssignmentToOsEnviron,
@@ -618,6 +673,7 @@ pub enum CheckKind {
UselessComparison,
UselessContextlibSuppress,
UselessExpression,
ZipWithoutExplicitStrict,
// flake8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
@@ -774,6 +830,14 @@ pub enum CheckKind {
BooleanPositionalValueInFunctionCall,
// pygrep-hooks
NoEval,
// flake8-unused-arguments
UnusedFunctionArgument(String),
UnusedMethodArgument(String),
UnusedClassMethodArgument(String),
UnusedStaticMethodArgument(String),
UnusedLambdaArgument(String),
// flake8-import-conventions
ImportAliasIsNotConventional(String, String),
// Ruff
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
@@ -886,7 +950,7 @@ impl CheckCode {
CheckCode::PLR1701 => {
CheckKind::ConsiderMergingIsinstance("...".to_string(), vec!["...".to_string()])
}
CheckCode::PLR1722 => CheckKind::ConsiderUsingSysExit,
CheckCode::PLR1722 => CheckKind::UseSysExit("exit".to_string()),
CheckCode::PLW0120 => CheckKind::UselessElseOnLoop,
// flake8-builtins
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
@@ -924,6 +988,7 @@ impl CheckCode {
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
CheckCode::B027 => CheckKind::EmptyMethodWithoutAbstractDecorator("...".to_string()),
CheckCode::B904 => CheckKind::RaiseWithoutFromInsideExcept,
CheckCode::B905 => CheckKind::ZipWithoutExplicitStrict,
// flake8-comprehensions
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
@@ -957,7 +1022,7 @@ impl CheckCode {
// flake8-debugger
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
// flake8-tidy-imports
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
CheckCode::TID252 => CheckKind::BannedRelativeImport(Strictness::All),
// flake8-return
CheckCode::RET501 => CheckKind::UnnecessaryReturnNone,
CheckCode::RET502 => CheckKind::ImplicitReturnValue,
@@ -999,7 +1064,7 @@ impl CheckCode {
CheckCode::YTT302 => CheckKind::SysVersionCmpStr10,
CheckCode::YTT303 => CheckKind::SysVersionSlice1Referenced,
// flake8-blind-except
CheckCode::BLE001 => CheckKind::BlindExcept,
CheckCode::BLE001 => CheckKind::BlindExcept("Exception".to_string()),
// pyupgrade
CheckCode::UP001 => CheckKind::UselessMetaclassType,
CheckCode::UP003 => CheckKind::TypeOfPrimitive(Primitive::Str),
@@ -1113,6 +1178,16 @@ impl CheckCode {
CheckCode::FBT003 => CheckKind::BooleanPositionalValueInFunctionCall,
// pygrep-hooks
CheckCode::PGH001 => CheckKind::NoEval,
// flake8-unused-arguments
CheckCode::ARG001 => CheckKind::UnusedFunctionArgument("...".to_string()),
CheckCode::ARG002 => CheckKind::UnusedMethodArgument("...".to_string()),
CheckCode::ARG003 => CheckKind::UnusedClassMethodArgument("...".to_string()),
CheckCode::ARG004 => CheckKind::UnusedStaticMethodArgument("...".to_string()),
CheckCode::ARG005 => CheckKind::UnusedLambdaArgument("...".to_string()),
// flake8-import-conventions
CheckCode::ICN001 => {
CheckKind::ImportAliasIsNotConventional("...".to_string(), "...".to_string())
}
// Ruff
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
@@ -1138,6 +1213,11 @@ impl CheckCode {
CheckCode::ANN205 => CheckCategory::Flake8Annotations,
CheckCode::ANN206 => CheckCategory::Flake8Annotations,
CheckCode::ANN401 => CheckCategory::Flake8Annotations,
CheckCode::ARG001 => CheckCategory::Flake8UnusedArguments,
CheckCode::ARG002 => CheckCategory::Flake8UnusedArguments,
CheckCode::ARG003 => CheckCategory::Flake8UnusedArguments,
CheckCode::ARG004 => CheckCategory::Flake8UnusedArguments,
CheckCode::ARG005 => CheckCategory::Flake8UnusedArguments,
CheckCode::B002 => CheckCategory::Flake8Bugbear,
CheckCode::B003 => CheckCategory::Flake8Bugbear,
CheckCode::B004 => CheckCategory::Flake8Bugbear,
@@ -1165,6 +1245,7 @@ impl CheckCode {
CheckCode::B026 => CheckCategory::Flake8Bugbear,
CheckCode::B027 => CheckCategory::Flake8Bugbear,
CheckCode::B904 => CheckCategory::Flake8Bugbear,
CheckCode::B905 => CheckCategory::Flake8Bugbear,
CheckCode::BLE001 => CheckCategory::Flake8BlindExcept,
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
@@ -1288,7 +1369,8 @@ impl CheckCode {
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::TID252 => CheckCategory::Flake8TidyImports,
CheckCode::ICN001 => CheckCategory::Flake8ImportConventions,
CheckCode::N801 => CheckCategory::PEP8Naming,
CheckCode::N802 => CheckCategory::PEP8Naming,
CheckCode::N803 => CheckCategory::PEP8Naming,
@@ -1441,7 +1523,7 @@ impl CheckKind {
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::ConsiderUsingFromImport(..) => &CheckCode::PLR0402,
CheckKind::ConsiderUsingSysExit => &CheckCode::PLR1722,
CheckKind::UseSysExit(_) => &CheckCode::PLR1722,
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
@@ -1465,6 +1547,7 @@ impl CheckKind {
CheckKind::MutableArgumentDefault => &CheckCode::B006,
CheckKind::NoAssertRaisesException => &CheckCode::B017,
CheckKind::RaiseWithoutFromInsideExcept => &CheckCode::B904,
CheckKind::ZipWithoutExplicitStrict => &CheckCode::B905,
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
CheckKind::SetAttrWithConstant => &CheckCode::B010,
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
@@ -1476,7 +1559,7 @@ impl CheckKind {
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
CheckKind::UselessExpression => &CheckCode::B018,
// flake8-blind-except
CheckKind::BlindExcept => &CheckCode::BLE001,
CheckKind::BlindExcept(_) => &CheckCode::BLE001,
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
@@ -1497,7 +1580,7 @@ impl CheckKind {
// flake8-debugger
CheckKind::Debugger(_) => &CheckCode::T100,
// flake8-tidy-imports
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
CheckKind::BannedRelativeImport(_) => &CheckCode::TID252,
// flake8-return
CheckKind::UnnecessaryReturnNone => &CheckCode::RET501,
CheckKind::ImplicitReturnValue => &CheckCode::RET502,
@@ -1633,6 +1716,14 @@ impl CheckKind {
CheckKind::BooleanPositionalValueInFunctionCall => &CheckCode::FBT003,
// pygrep-hooks
CheckKind::NoEval => &CheckCode::PGH001,
// flake8-unused-arguments
CheckKind::UnusedFunctionArgument(..) => &CheckCode::ARG001,
CheckKind::UnusedMethodArgument(..) => &CheckCode::ARG002,
CheckKind::UnusedClassMethodArgument(..) => &CheckCode::ARG003,
CheckKind::UnusedStaticMethodArgument(..) => &CheckCode::ARG004,
CheckKind::UnusedLambdaArgument(..) => &CheckCode::ARG005,
// flake8-import-conventions
CheckKind::ImportAliasIsNotConventional(..) => &CheckCode::ICN001,
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
@@ -1830,7 +1921,7 @@ impl CheckKind {
}
CheckKind::ConsiderMergingIsinstance(obj, types) => {
let types = types.join(", ");
format!("Consider merging these isinstance calls: `isinstance({obj}, ({types}))`")
format!("Merge these isinstance calls: `isinstance({obj}, ({types}))`")
}
CheckKind::MisplacedComparisonConstant(comprison) => {
format!("Comparison should be {comprison}")
@@ -1842,7 +1933,7 @@ impl CheckKind {
"Cannot have defined parameters for properties".to_string()
}
CheckKind::ConsiderUsingFromImport(module, name) => {
format!("Consider using `from {module} import {name}`")
format!("Use `from {module} import {name}` in lieu of alias")
}
CheckKind::AwaitOutsideAsync => {
"`await` should be used within an async function".to_string()
@@ -1850,7 +1941,7 @@ impl CheckKind {
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(),
CheckKind::UseSysExit(name) => format!("Use `sys.exit()` instead of `{name}`"),
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
@@ -1976,6 +2067,9 @@ impl CheckKind {
from None to distinguish them from errors in exception handling"
.to_string()
}
CheckKind::ZipWithoutExplicitStrict => {
"`zip()` without an explicit `strict=` parameter".to_string()
}
// flake8-comprehensions
CheckKind::UnnecessaryGeneratorList => {
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
@@ -2402,7 +2496,7 @@ impl CheckKind {
format!("Possible hardcoded password: `\"{string}\"`")
}
// flake8-blind-except
CheckKind::BlindExcept => "Blind except Exception: statement".to_string(),
CheckKind::BlindExcept(name) => format!("Do not catch blind exception: `{name}`"),
// mccabe
CheckKind::FunctionIsTooComplex(name, complexity) => {
format!("`{name}` is too complex ({complexity})")
@@ -2419,6 +2513,22 @@ impl CheckKind {
}
// pygrep-hooks
CheckKind::NoEval => "No builtin `eval()` allowed".to_string(),
// flake8-unused-arguments
CheckKind::UnusedFunctionArgument(name) => {
format!("Unused function argument: `{name}`")
}
CheckKind::UnusedMethodArgument(name) => format!("Unused method argument: `{name}`"),
CheckKind::UnusedClassMethodArgument(name) => {
format!("Unused class method argument: `{name}`")
}
CheckKind::UnusedStaticMethodArgument(name) => {
format!("Unused static method argument: `{name}`")
}
CheckKind::UnusedLambdaArgument(name) => format!("Unused lambda argument: `{name}`"),
// flake8-import-conventions
CheckKind::ImportAliasIsNotConventional(name, asname) => {
format!("`{name}` should be imported as `{asname}`")
}
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
format!(
@@ -2489,7 +2599,6 @@ impl CheckKind {
| CheckKind::BlankLineBeforeSection(..)
| CheckKind::CapitalizeSectionName(..)
| CheckKind::CommentedOutCode
| CheckKind::ConsiderUsingSysExit
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::DashedUnderlineAfterSection(..)
@@ -2555,6 +2664,7 @@ impl CheckKind {
| CheckKind::UnusedNOQA(..)
| CheckKind::UsePEP585Annotation(..)
| CheckKind::UsePEP604Annotation
| CheckKind::UseSysExit(..)
| CheckKind::UselessImportAlias
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(..)
@@ -2586,7 +2696,7 @@ impl Check {
}
/// A hash map from deprecated to latest `CheckCode`.
pub static REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U001", CheckCode::UP001),
@@ -2603,6 +2713,25 @@ pub static REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
("U013", CheckCode::UP013),
("U014", CheckCode::UP014),
("U015", CheckCode::UP015),
// TODO(charlie): Remove by 2023-02-01.
("I252", CheckCode::TID252),
("M001", CheckCode::RUF100),
])
});
/// A hash map from deprecated `CheckCodePrefix` to latest `CheckCodePrefix`.
pub static PREFIX_REDIRECTS: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U", "UP"),
("U0", "UP0"),
("U00", "UP00"),
("U01", "UP01"),
// TODO(charlie): Remove by 2023-02-01.
("I2", "TID2"),
("I25", "TID25"),
("M", "RUF100"),
("M0", "RUF100"),
])
});

View File

@@ -2,11 +2,13 @@
use colored::Colorize;
use serde::{Deserialize, Serialize};
use strum_macros::EnumString;
use strum_macros::{AsRefStr, EnumString};
use crate::checks::CheckCode;
#[derive(EnumString, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize)]
#[derive(
EnumString, AsRefStr, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Serialize, Deserialize,
)]
pub enum CheckCodePrefix {
A,
A0,
@@ -34,6 +36,14 @@ pub enum CheckCodePrefix {
ANN4,
ANN40,
ANN401,
ARG,
ARG0,
ARG00,
ARG001,
ARG002,
ARG003,
ARG004,
ARG005,
B,
B0,
B00,
@@ -68,6 +78,7 @@ pub enum CheckCodePrefix {
B9,
B90,
B904,
B905,
BLE,
BLE0,
BLE00,
@@ -256,6 +267,13 @@ pub enum CheckCodePrefix {
I2,
I25,
I252,
ICN,
ICN0,
ICN00,
ICN001,
M,
M0,
M001,
N,
N8,
N80,
@@ -360,6 +378,10 @@ pub enum CheckCodePrefix {
T20,
T201,
T203,
TID,
TID2,
TID25,
TID252,
U,
U0,
U00,
@@ -485,6 +507,32 @@ impl CheckCodePrefix {
CheckCodePrefix::ANN4 => vec![CheckCode::ANN401],
CheckCodePrefix::ANN40 => vec![CheckCode::ANN401],
CheckCodePrefix::ANN401 => vec![CheckCode::ANN401],
CheckCodePrefix::ARG => vec![
CheckCode::ARG001,
CheckCode::ARG002,
CheckCode::ARG003,
CheckCode::ARG004,
CheckCode::ARG005,
],
CheckCodePrefix::ARG0 => vec![
CheckCode::ARG001,
CheckCode::ARG002,
CheckCode::ARG003,
CheckCode::ARG004,
CheckCode::ARG005,
],
CheckCodePrefix::ARG00 => vec![
CheckCode::ARG001,
CheckCode::ARG002,
CheckCode::ARG003,
CheckCode::ARG004,
CheckCode::ARG005,
],
CheckCodePrefix::ARG001 => vec![CheckCode::ARG001],
CheckCodePrefix::ARG002 => vec![CheckCode::ARG002],
CheckCodePrefix::ARG003 => vec![CheckCode::ARG003],
CheckCodePrefix::ARG004 => vec![CheckCode::ARG004],
CheckCodePrefix::ARG005 => vec![CheckCode::ARG005],
CheckCodePrefix::B => vec![
CheckCode::B002,
CheckCode::B003,
@@ -513,6 +561,7 @@ impl CheckCodePrefix {
CheckCode::B026,
CheckCode::B027,
CheckCode::B904,
CheckCode::B905,
],
CheckCodePrefix::B0 => vec![
CheckCode::B002,
@@ -600,9 +649,10 @@ impl CheckCodePrefix {
CheckCodePrefix::B025 => vec![CheckCode::B025],
CheckCodePrefix::B026 => vec![CheckCode::B026],
CheckCodePrefix::B027 => vec![CheckCode::B027],
CheckCodePrefix::B9 => vec![CheckCode::B904],
CheckCodePrefix::B90 => vec![CheckCode::B904],
CheckCodePrefix::B9 => vec![CheckCode::B904, CheckCode::B905],
CheckCodePrefix::B90 => vec![CheckCode::B904, CheckCode::B905],
CheckCodePrefix::B904 => vec![CheckCode::B904],
CheckCodePrefix::B905 => vec![CheckCode::B905],
CheckCodePrefix::BLE => vec![CheckCode::BLE001],
CheckCodePrefix::BLE0 => vec![CheckCode::BLE001],
CheckCodePrefix::BLE00 => vec![CheckCode::BLE001],
@@ -1131,13 +1181,68 @@ impl CheckCodePrefix {
CheckCodePrefix::FBT001 => vec![CheckCode::FBT001],
CheckCodePrefix::FBT002 => vec![CheckCode::FBT002],
CheckCodePrefix::FBT003 => vec![CheckCode::FBT003],
CheckCodePrefix::I => vec![CheckCode::I252, CheckCode::I001],
CheckCodePrefix::I => vec![CheckCode::I001],
CheckCodePrefix::I0 => vec![CheckCode::I001],
CheckCodePrefix::I00 => vec![CheckCode::I001],
CheckCodePrefix::I001 => vec![CheckCode::I001],
CheckCodePrefix::I2 => vec![CheckCode::I252],
CheckCodePrefix::I25 => vec![CheckCode::I252],
CheckCodePrefix::I252 => vec![CheckCode::I252],
CheckCodePrefix::I2 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`I2` has been remapped to `TID2`".bold()
);
vec![CheckCode::TID252]
}
CheckCodePrefix::I25 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`I25` has been remapped to `TID25`".bold()
);
vec![CheckCode::TID252]
}
CheckCodePrefix::I252 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`I252` has been remapped to `TID252`".bold()
);
vec![CheckCode::TID252]
}
CheckCodePrefix::ICN => vec![CheckCode::ICN001],
CheckCodePrefix::ICN0 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN001 => vec![CheckCode::ICN001],
CheckCodePrefix::M => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`M` has been remapped to `RUF100`".bold()
);
vec![CheckCode::RUF100]
}
CheckCodePrefix::M0 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`M0` has been remapped to `RUF100`".bold()
);
vec![CheckCode::RUF100]
}
CheckCodePrefix::M001 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`M001` has been remapped to `RUF100`".bold()
);
vec![CheckCode::RUF100]
}
CheckCodePrefix::N => vec![
CheckCode::N801,
CheckCode::N802,
@@ -1366,54 +1471,82 @@ impl CheckCodePrefix {
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T201 => vec![CheckCode::T201],
CheckCodePrefix::T203 => vec![CheckCode::T203],
CheckCodePrefix::U => 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::U0 => 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::U00 => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
],
CheckCodePrefix::TID => vec![CheckCode::TID252],
CheckCodePrefix::TID2 => vec![CheckCode::TID252],
CheckCodePrefix::TID25 => vec![CheckCode::TID252],
CheckCodePrefix::TID252 => vec![CheckCode::TID252],
CheckCodePrefix::U => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U` has been remapped to `UP`".bold()
);
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::U0 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U0` has been remapped to `UP0`".bold()
);
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::U00 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U00` has been remapped to `UP00`".bold()
);
vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
]
}
CheckCodePrefix::U001 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U001` has been renamed to `UP001`".bold()
"`U001` has been remapped to `UP001`".bold()
);
vec![CheckCode::UP001]
}
@@ -1422,7 +1555,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U003` has been renamed to `UP003`".bold()
"`U003` has been remapped to `UP003`".bold()
);
vec![CheckCode::UP003]
}
@@ -1431,7 +1564,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U004` has been renamed to `UP004`".bold()
"`U004` has been remapped to `UP004`".bold()
);
vec![CheckCode::UP004]
}
@@ -1440,7 +1573,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U005` has been renamed to `UP005`".bold()
"`U005` has been remapped to `UP005`".bold()
);
vec![CheckCode::UP005]
}
@@ -1449,7 +1582,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U006` has been renamed to `UP006`".bold()
"`U006` has been remapped to `UP006`".bold()
);
vec![CheckCode::UP006]
}
@@ -1458,7 +1591,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U007` has been renamed to `UP007`".bold()
"`U007` has been remapped to `UP007`".bold()
);
vec![CheckCode::UP007]
}
@@ -1467,7 +1600,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U008` has been renamed to `UP008`".bold()
"`U008` has been remapped to `UP008`".bold()
);
vec![CheckCode::UP008]
}
@@ -1476,24 +1609,32 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U009` has been renamed to `UP009`".bold()
"`U009` has been remapped to `UP009`".bold()
);
vec![CheckCode::UP009]
}
CheckCodePrefix::U01 => vec![
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U01 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U01` has been remapped to `UP01`".bold()
);
vec![
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
]
}
CheckCodePrefix::U010 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U010` has been renamed to `UP010`".bold()
"`U010` has been remapped to `UP010`".bold()
);
vec![CheckCode::UP010]
}
@@ -1502,7 +1643,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U011` has been renamed to `UP011`".bold()
"`U011` has been remapped to `UP011`".bold()
);
vec![CheckCode::UP011]
}
@@ -1511,7 +1652,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U012` has been renamed to `UP012`".bold()
"`U012` has been remapped to `UP012`".bold()
);
vec![CheckCode::UP012]
}
@@ -1520,7 +1661,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U013` has been renamed to `UP013`".bold()
"`U013` has been remapped to `UP013`".bold()
);
vec![CheckCode::UP013]
}
@@ -1529,7 +1670,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U014` has been renamed to `UP014`".bold()
"`U014` has been remapped to `UP014`".bold()
);
vec![CheckCode::UP014]
}
@@ -1538,7 +1679,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U015` has been renamed to `UP015`".bold()
"`U015` has been remapped to `UP015`".bold()
);
vec![CheckCode::UP015]
}
@@ -1685,6 +1826,14 @@ impl CheckCodePrefix {
CheckCodePrefix::ANN4 => SuffixLength::One,
CheckCodePrefix::ANN40 => SuffixLength::Two,
CheckCodePrefix::ANN401 => SuffixLength::Three,
CheckCodePrefix::ARG => SuffixLength::Zero,
CheckCodePrefix::ARG0 => SuffixLength::One,
CheckCodePrefix::ARG00 => SuffixLength::Two,
CheckCodePrefix::ARG001 => SuffixLength::Three,
CheckCodePrefix::ARG002 => SuffixLength::Three,
CheckCodePrefix::ARG003 => SuffixLength::Three,
CheckCodePrefix::ARG004 => SuffixLength::Three,
CheckCodePrefix::ARG005 => SuffixLength::Three,
CheckCodePrefix::B => SuffixLength::Zero,
CheckCodePrefix::B0 => SuffixLength::One,
CheckCodePrefix::B00 => SuffixLength::Two,
@@ -1719,6 +1868,7 @@ impl CheckCodePrefix {
CheckCodePrefix::B9 => SuffixLength::One,
CheckCodePrefix::B90 => SuffixLength::Two,
CheckCodePrefix::B904 => SuffixLength::Three,
CheckCodePrefix::B905 => SuffixLength::Three,
CheckCodePrefix::BLE => SuffixLength::Zero,
CheckCodePrefix::BLE0 => SuffixLength::One,
CheckCodePrefix::BLE00 => SuffixLength::Two,
@@ -1907,6 +2057,13 @@ impl CheckCodePrefix {
CheckCodePrefix::I2 => SuffixLength::One,
CheckCodePrefix::I25 => SuffixLength::Two,
CheckCodePrefix::I252 => SuffixLength::Three,
CheckCodePrefix::ICN => SuffixLength::Zero,
CheckCodePrefix::ICN0 => SuffixLength::One,
CheckCodePrefix::ICN00 => SuffixLength::Two,
CheckCodePrefix::ICN001 => SuffixLength::Three,
CheckCodePrefix::M => SuffixLength::Zero,
CheckCodePrefix::M0 => SuffixLength::One,
CheckCodePrefix::M001 => SuffixLength::Three,
CheckCodePrefix::N => SuffixLength::Zero,
CheckCodePrefix::N8 => SuffixLength::One,
CheckCodePrefix::N80 => SuffixLength::Two,
@@ -2011,6 +2168,10 @@ impl CheckCodePrefix {
CheckCodePrefix::T20 => SuffixLength::Two,
CheckCodePrefix::T201 => SuffixLength::Three,
CheckCodePrefix::T203 => SuffixLength::Three,
CheckCodePrefix::TID => SuffixLength::Zero,
CheckCodePrefix::TID2 => SuffixLength::One,
CheckCodePrefix::TID25 => SuffixLength::Two,
CheckCodePrefix::TID252 => SuffixLength::Three,
CheckCodePrefix::U => SuffixLength::Zero,
CheckCodePrefix::U0 => SuffixLength::One,
CheckCodePrefix::U00 => SuffixLength::Two,
@@ -2078,6 +2239,7 @@ impl CheckCodePrefix {
pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::A,
CheckCodePrefix::ANN,
CheckCodePrefix::ARG,
CheckCodePrefix::B,
CheckCodePrefix::BLE,
CheckCodePrefix::C,
@@ -2087,6 +2249,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::F,
CheckCodePrefix::FBT,
CheckCodePrefix::I,
CheckCodePrefix::ICN,
CheckCodePrefix::N,
CheckCodePrefix::PGH,
CheckCodePrefix::PLC,
@@ -2098,7 +2261,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::RUF,
CheckCodePrefix::S,
CheckCodePrefix::T,
CheckCodePrefix::U,
CheckCodePrefix::TID,
CheckCodePrefix::UP,
CheckCodePrefix::W,
CheckCodePrefix::YTT,

View File

@@ -93,32 +93,34 @@ pub fn extract_isort_directives(lxr: &[LexResult], locator: &SourceCodeLocator)
continue;
}
if matches!(tok, Tok::Comment) {
// TODO(charlie): Modify RustPython to include the comment text in the token.
let comment_text = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
if !matches!(tok, Tok::Comment) {
continue;
}
if comment_text == "# isort: split" {
splits.push(start.row());
} else if comment_text == "# isort: skip_file" {
skip_file = true;
} else if off.is_some() {
if comment_text == "# isort: on" {
if let Some(start) = off {
for row in start.row() + 1..=end.row() {
exclusions.insert(row);
}
// TODO(charlie): Modify RustPython to include the comment text in the token.
let comment_text = locator.slice_source_code_range(&Range {
location: start,
end_location: end,
});
if comment_text == "# isort: split" {
splits.push(start.row());
} else if comment_text == "# isort: skip_file" {
skip_file = true;
} else if off.is_some() {
if comment_text == "# isort: on" {
if let Some(start) = off {
for row in start.row() + 1..=end.row() {
exclusions.insert(row);
}
off = None;
}
} else {
if comment_text.contains("isort: skip") {
exclusions.insert(start.row());
} else if comment_text == "# isort: off" {
off = Some(start);
}
off = None;
}
} else {
if comment_text.contains("isort: skip") {
exclusions.insert(start.row());
} else if comment_text == "# isort: off" {
off = Some(start);
}
}
}

View File

@@ -0,0 +1,63 @@
use rustpython_ast::{Arguments, Expr, Stmt, StmtKind};
use crate::ast::cast;
use crate::check_ast::Checker;
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::visibility;
pub(super) fn match_function_def(
stmt: &Stmt,
) -> (&str, &Arguments, &Option<Box<Expr>>, &Vec<Stmt>) {
match &stmt.node {
StmtKind::FunctionDef {
name,
args,
returns,
body,
..
}
| StmtKind::AsyncFunctionDef {
name,
args,
returns,
body,
..
} => (name, args, returns, body),
_ => panic!("Found non-FunctionDef in match_name"),
}
}
/// Return the name of the function, if it's overloaded.
pub fn overloaded_name(checker: &Checker, definition: &Definition) -> Option<String> {
if let DefinitionKind::Function(stmt)
| DefinitionKind::NestedFunction(stmt)
| DefinitionKind::Method(stmt) = definition.kind
{
if visibility::is_overload(checker, cast::decorator_list(stmt)) {
let (name, ..) = match_function_def(stmt);
Some(name.to_string())
} else {
None
}
} else {
None
}
}
/// Return `true` if the definition is the implementation for an overloaded
/// function.
pub fn is_overload_impl(checker: &Checker, definition: &Definition, overloaded_name: &str) -> bool {
if let DefinitionKind::Function(stmt)
| DefinitionKind::NestedFunction(stmt)
| DefinitionKind::Method(stmt) = definition.kind
{
if visibility::is_overload(checker, cast::decorator_list(stmt)) {
false
} else {
let (name, ..) = match_function_def(stmt);
name == overloaded_name
}
} else {
false
}
}

View File

@@ -1,3 +1,4 @@
pub mod helpers;
pub mod plugins;
pub mod settings;
@@ -134,4 +135,24 @@ mod tests {
insta::assert_yaml_snapshot!(checks);
Ok(())
}
#[test]
fn allow_overload() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_annotations/allow_overload.py"),
&Settings {
..Settings::for_rules(vec![
CheckCode::ANN201,
CheckCode::ANN202,
CheckCode::ANN204,
CheckCode::ANN205,
CheckCode::ANN206,
])
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!(checks);
Ok(())
}
}

View File

@@ -1,11 +1,12 @@
use rustpython_ast::{Arguments, Constant, Expr, ExprKind, Stmt, StmtKind};
use rustpython_ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
use crate::ast::{cast, visitor};
use crate::check_ast::Checker;
use crate::checks::{CheckCode, CheckKind};
use crate::docstrings::definition::{Definition, DefinitionKind};
use crate::flake8_annotations::helpers::match_function_def;
use crate::visibility::Visibility;
use crate::{visibility, Check};
@@ -61,26 +62,6 @@ where
};
}
fn match_function_def(stmt: &Stmt) -> (&str, &Arguments, &Option<Box<Expr>>, &Vec<Stmt>) {
match &stmt.node {
StmtKind::FunctionDef {
name,
args,
returns,
body,
..
}
| StmtKind::AsyncFunctionDef {
name,
args,
returns,
body,
..
} => (name, args, returns, body),
_ => panic!("Found non-FunctionDef in match_name"),
}
}
/// Generate flake8-annotation checks for a given `Definition`.
pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &Visibility) {
// TODO(charlie): Consider using the AST directly here rather than `Definition`.
@@ -211,7 +192,10 @@ 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(checker, stmt)),
usize::from(!visibility::is_staticmethod(
checker,
cast::decorator_list(stmt),
)),
)
{
// ANN401 for dynamically typed arguments
@@ -283,10 +267,10 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
}
// ANN101, ANN102
if !visibility::is_staticmethod(checker, stmt) {
if !visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
if let Some(arg) = args.args.first() {
if arg.node.annotation.is_none() {
if visibility::is_classmethod(checker, stmt) {
if visibility::is_classmethod(checker, cast::decorator_list(stmt)) {
if checker.settings.enabled.contains(&CheckCode::ANN102) {
checker.add_check(Check::new(
CheckKind::MissingTypeCls(arg.node.arg.to_string()),
@@ -319,14 +303,14 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
return;
}
if visibility::is_classmethod(checker, stmt) {
if visibility::is_classmethod(checker, cast::decorator_list(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(checker, stmt) {
} else if visibility::is_staticmethod(checker, cast::decorator_list(stmt)) {
if checker.settings.enabled.contains(&CheckCode::ANN205) {
checker.add_check(Check::new(
CheckKind::MissingReturnTypeStaticMethod(name.to_string()),

View File

@@ -0,0 +1,14 @@
---
source: src/flake8_annotations/mod.rs
expression: checks
---
- kind:
MissingReturnTypePublicFunction: bar
location:
row: 29
column: 4
end_location:
row: 35
column: 0
fix: ~

View File

@@ -1,21 +1,42 @@
use rustpython_ast::{Excepthandler, ExcepthandlerKind, ExprKind};
use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
pub fn blind_except(checker: &mut Checker, handlers: &[Excepthandler]) {
for handler in handlers {
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
continue;
};
let ExprKind::Name { id, .. } = &type_.node else {
continue;
};
for exception in ["BaseException", "Exception"] {
if id == exception {
/// BLE001
pub fn blind_except(
checker: &mut Checker,
type_: Option<&Expr>,
name: Option<&str>,
body: &[Stmt],
) {
let Some(type_) = type_ else {
return;
};
let ExprKind::Name { id, .. } = &type_.node else {
return;
};
for exception in ["BaseException", "Exception"] {
if id == exception && checker.is_builtin(exception) {
// If the exception is re-raised, don't flag an error.
if !body.iter().any(|stmt| {
if let StmtKind::Raise { exc, .. } = &stmt.node {
if let Some(exc) = exc {
if let ExprKind::Name { id, .. } = &exc.node {
name.map_or(false, |name| name == id)
} else {
false
}
} else {
true
}
} else {
false
}
}) {
checker.add_check(Check::new(
CheckKind::BlindExcept,
CheckKind::BlindExcept(id.to_string()),
Range::from_located(type_),
));
}

View File

@@ -2,31 +2,8 @@
source: src/flake8_blind_except/mod.rs
expression: checks
---
- kind: BlindExcept
location:
row: 5
column: 7
end_location:
row: 5
column: 16
fix: ~
- kind: BlindExcept
location:
row: 13
column: 7
end_location:
row: 13
column: 20
fix: ~
- kind: BlindExcept
location:
row: 23
column: 7
end_location:
row: 23
column: 16
fix: ~
- kind: BlindExcept
- kind:
BlindExcept: BaseException
location:
row: 25
column: 7
@@ -34,7 +11,8 @@ expression: checks
row: 25
column: 20
fix: ~
- kind: BlindExcept
- kind:
BlindExcept: Exception
location:
row: 31
column: 7
@@ -42,15 +20,8 @@ expression: checks
row: 31
column: 16
fix: ~
- kind: BlindExcept
location:
row: 36
column: 11
end_location:
row: 36
column: 24
fix: ~
- kind: BlindExcept
- kind:
BlindExcept: Exception
location:
row: 42
column: 7
@@ -58,7 +29,8 @@ expression: checks
row: 42
column: 16
fix: ~
- kind: BlindExcept
- kind:
BlindExcept: BaseException
location:
row: 45
column: 11
@@ -66,15 +38,8 @@ expression: checks
row: 45
column: 24
fix: ~
- kind: BlindExcept
location:
row: 52
column: 11
end_location:
row: 52
column: 24
fix: ~
- kind: BlindExcept
- kind:
BlindExcept: Exception
location:
row: 54
column: 7
@@ -82,4 +47,22 @@ expression: checks
row: 54
column: 16
fix: ~
- kind:
BlindExcept: Exception
location:
row: 60
column: 7
end_location:
row: 60
column: 16
fix: ~
- kind:
BlindExcept: BaseException
location:
row: 62
column: 7
end_location:
row: 62
column: 20
fix: ~

View File

@@ -39,6 +39,7 @@ mod tests {
#[test_case(CheckCode::B026, Path::new("B026.py"); "B026")]
#[test_case(CheckCode::B027, Path::new("B027.py"); "B027")]
#[test_case(CheckCode::B904, Path::new("B904.py"); "B904")]
#[test_case(CheckCode::B905, Path::new("B905.py"); "B905")]
fn checks(check_code: CheckCode, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
let mut checks = test_path(

View File

@@ -1,6 +1,5 @@
use std::collections::BTreeSet;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use rustpython_ast::{
Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind, Location, Stmt,
};
@@ -27,9 +26,9 @@ fn duplicate_handler_exceptions<'a>(
checker: &mut Checker,
expr: &'a Expr,
elts: &'a [Expr],
) -> BTreeSet<Vec<&'a str>> {
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
) -> FxHashSet<Vec<&'a str>> {
let mut seen: FxHashSet<Vec<&str>> = FxHashSet::default();
let mut duplicates: FxHashSet<Vec<&str>> = FxHashSet::default();
let mut unique_elts: Vec<&Expr> = Vec::default();
for type_ in elts {
let call_path = helpers::collect_call_paths(type_);
@@ -76,8 +75,8 @@ fn duplicate_handler_exceptions<'a>(
}
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
let mut seen: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut duplicates: BTreeSet<Vec<&str>> = BTreeSet::default();
let mut seen: FxHashSet<Vec<&str>> = FxHashSet::default();
let mut duplicates: FxHashSet<Vec<&str>> = FxHashSet::default();
for handler in handlers {
let ExcepthandlerKind::ExceptHandler { type_: Some(type_), .. } = &handler.node else {
continue;

View File

@@ -23,6 +23,7 @@ pub use unused_loop_control_variable::unused_loop_control_variable;
pub use useless_comparison::useless_comparison;
pub use useless_contextlib_suppress::useless_contextlib_suppress;
pub use useless_expression::useless_expression;
pub use zip_without_explicit_strict::zip_without_explicit_strict;
mod abstract_base_class;
mod assert_false;
@@ -49,3 +50,4 @@ mod unused_loop_control_variable;
mod useless_comparison;
mod useless_contextlib_suppress;
mod useless_expression;
mod zip_without_explicit_strict;

View File

@@ -0,0 +1,31 @@
use rustpython_ast::{Expr, ExprKind, Keyword};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
/// B905
pub fn zip_without_explicit_strict(
checker: &mut Checker,
expr: &Expr,
func: &Expr,
kwargs: &[Keyword],
) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "zip"
&& checker.is_builtin("zip")
&& !kwargs.iter().any(|keyword| {
keyword
.node
.arg
.as_ref()
.map_or(false, |name| name == "strict")
})
{
checker.add_check(Check::new(
CheckKind::ZipWithoutExplicitStrict,
Range::from_located(expr),
));
}
}
}

View File

@@ -0,0 +1,61 @@
---
source: src/flake8_bugbear/mod.rs
expression: checks
---
- kind: ZipWithoutExplicitStrict
location:
row: 1
column: 0
end_location:
row: 1
column: 5
fix: ~
- kind: ZipWithoutExplicitStrict
location:
row: 2
column: 0
end_location:
row: 2
column: 13
fix: ~
- kind: ZipWithoutExplicitStrict
location:
row: 3
column: 0
end_location:
row: 3
column: 13
fix: ~
- kind: ZipWithoutExplicitStrict
location:
row: 4
column: 0
end_location:
row: 4
column: 24
fix: ~
- kind: ZipWithoutExplicitStrict
location:
row: 4
column: 15
end_location:
row: 4
column: 23
fix: ~
- kind: ZipWithoutExplicitStrict
location:
row: 5
column: 4
end_location:
row: 5
column: 12
fix: ~
- kind: ZipWithoutExplicitStrict
location:
row: 6
column: 0
end_location:
row: 6
column: 26
fix: ~

View File

@@ -0,0 +1,36 @@
use rustc_hash::FxHashMap;
use rustpython_ast::Stmt;
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
/// ICN001
pub fn check_conventional_import(
import_from: &Stmt,
name: &str,
asname: Option<&str>,
conventions: &FxHashMap<String, String>,
) -> Option<Check> {
let mut is_valid_import = true;
if let Some(expected_alias) = conventions.get(name) {
if !expected_alias.is_empty() {
if let Some(alias) = asname {
if expected_alias != alias {
is_valid_import = false;
}
} else {
is_valid_import = false;
}
}
if !is_valid_import {
return Some(Check::new(
CheckKind::ImportAliasIsNotConventional(
name.to_string(),
expected_alias.to_string(),
),
Range::from_located(import_from),
));
}
}
None
}

View File

@@ -0,0 +1,101 @@
pub mod checks;
pub mod settings;
#[cfg(test)]
mod tests {
use std::path::Path;
use anyhow::Result;
use rustc_hash::FxHashMap;
use crate::checks::CheckCode;
use crate::linter::test_path;
use crate::{flake8_import_conventions, Settings};
#[test]
fn defaults() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_import_conventions/defaults.py"),
&Settings::for_rule(CheckCode::ICN001),
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("defaults", checks);
Ok(())
}
#[test]
fn custom() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_import_conventions/custom.py"),
&Settings {
flake8_import_conventions:
flake8_import_conventions::settings::Settings::from_options(
flake8_import_conventions::settings::Options {
aliases: None,
extend_aliases: Some(FxHashMap::from_iter([
("dask.array".to_string(), "da".to_string()),
("dask.dataframe".to_string(), "dd".to_string()),
])),
},
),
..Settings::for_rule(CheckCode::ICN001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("custom", checks);
Ok(())
}
#[test]
fn remove_defaults() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_import_conventions/remove_default.py"),
&Settings {
flake8_import_conventions:
flake8_import_conventions::settings::Settings::from_options(
flake8_import_conventions::settings::Options {
aliases: Some(FxHashMap::from_iter([
("altair".to_string(), "alt".to_string()),
("matplotlib.pyplot".to_string(), "plt".to_string()),
("pandas".to_string(), "pd".to_string()),
("seaborn".to_string(), "sns".to_string()),
])),
extend_aliases: None,
},
),
..Settings::for_rule(CheckCode::ICN001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("remove_default", checks);
Ok(())
}
#[test]
fn override_defaults() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_import_conventions/override_default.py"),
&Settings {
flake8_import_conventions:
flake8_import_conventions::settings::Settings::from_options(
flake8_import_conventions::settings::Options {
aliases: None,
extend_aliases: Some(FxHashMap::from_iter([(
"numpy".to_string(),
"nmp".to_string(),
)])),
},
),
..Settings::for_rule(CheckCode::ICN001)
},
true,
)?;
checks.sort_by_key(|check| check.location);
insta::assert_yaml_snapshot!("override_default", checks);
Ok(())
}
}

View File

@@ -0,0 +1,94 @@
//! Settings for import conventions.
use std::hash::{Hash, Hasher};
use itertools::Itertools;
use ruff_macros::ConfigurationOptions;
use rustc_hash::FxHashMap;
use serde::{Deserialize, Serialize};
const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
("altair", "alt"),
("matplotlib.pyplot", "plt"),
("numpy", "np"),
("pandas", "pd"),
("seaborn", "sns"),
];
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct Options {
#[option(
doc = "The conventional aliases for imports. These aliases can be extended by the \
`extend_aliases` option.",
default = r#"{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}"#,
value_type = "FxHashMap<String, String>",
example = r#"
# Declare the default aliases.
altair = "alt"
matplotlib.pyplot = "plt"
numpy = "np"
pandas = "pd"
seaborn = "sns"
"#
)]
pub aliases: Option<FxHashMap<String, String>>,
#[option(
doc = "A mapping of modules to their conventional import aliases. These aliases will be \
added to the `aliases` mapping.",
default = r#"{}"#,
value_type = "FxHashMap<String, String>",
example = r#"
# Declare a custom alias for the `matplotlib` module.
"dask.dataframe" = "dd"
"#
)]
pub extend_aliases: Option<FxHashMap<String, String>>,
}
#[derive(Debug)]
pub struct Settings {
pub aliases: FxHashMap<String, String>,
}
impl Hash for Settings {
fn hash<H: Hasher>(&self, state: &mut H) {
for value in self.aliases.iter().sorted() {
value.hash(state);
}
}
}
fn default_aliases() -> FxHashMap<String, String> {
CONVENTIONAL_ALIASES
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect::<FxHashMap<_, _>>()
}
fn resolve_aliases(options: Options) -> FxHashMap<String, String> {
let mut aliases = match options.aliases {
Some(options_aliases) => options_aliases,
None => default_aliases(),
};
if let Some(extend_aliases) = options.extend_aliases {
aliases.extend(extend_aliases);
}
aliases
}
impl Settings {
pub fn from_options(options: Options) -> Self {
Self {
aliases: resolve_aliases(options),
}
}
}
impl Default for Settings {
fn default() -> Self {
Self {
aliases: default_aliases(),
}
}
}

View File

@@ -0,0 +1,159 @@
---
source: src/flake8_import_conventions/mod.rs
expression: checks
---
- kind:
ImportAliasIsNotConventional:
- altair
- alt
location:
row: 3
column: 0
end_location:
row: 3
column: 13
fix: ~
- kind:
ImportAliasIsNotConventional:
- dask.array
- da
location:
row: 4
column: 0
end_location:
row: 4
column: 17
fix: ~
- kind:
ImportAliasIsNotConventional:
- dask.dataframe
- dd
location:
row: 5
column: 0
end_location:
row: 5
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- matplotlib.pyplot
- plt
location:
row: 6
column: 0
end_location:
row: 6
column: 24
fix: ~
- kind:
ImportAliasIsNotConventional:
- numpy
- np
location:
row: 7
column: 0
end_location:
row: 7
column: 12
fix: ~
- kind:
ImportAliasIsNotConventional:
- pandas
- pd
location:
row: 8
column: 0
end_location:
row: 8
column: 13
fix: ~
- kind:
ImportAliasIsNotConventional:
- seaborn
- sns
location:
row: 9
column: 0
end_location:
row: 9
column: 14
fix: ~
- kind:
ImportAliasIsNotConventional:
- altair
- alt
location:
row: 11
column: 0
end_location:
row: 11
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- matplotlib.pyplot
- plt
location:
row: 12
column: 0
end_location:
row: 12
column: 32
fix: ~
- kind:
ImportAliasIsNotConventional:
- dask.array
- da
location:
row: 13
column: 0
end_location:
row: 13
column: 27
fix: ~
- kind:
ImportAliasIsNotConventional:
- dask.dataframe
- dd
location:
row: 14
column: 0
end_location:
row: 14
column: 28
fix: ~
- kind:
ImportAliasIsNotConventional:
- numpy
- np
location:
row: 15
column: 0
end_location:
row: 15
column: 19
fix: ~
- kind:
ImportAliasIsNotConventional:
- pandas
- pd
location:
row: 16
column: 0
end_location:
row: 16
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- seaborn
- sns
location:
row: 17
column: 0
end_location:
row: 17
column: 22
fix: ~

View File

@@ -0,0 +1,115 @@
---
source: src/flake8_import_conventions/mod.rs
expression: checks
---
- kind:
ImportAliasIsNotConventional:
- altair
- alt
location:
row: 3
column: 0
end_location:
row: 3
column: 13
fix: ~
- kind:
ImportAliasIsNotConventional:
- matplotlib.pyplot
- plt
location:
row: 4
column: 0
end_location:
row: 4
column: 24
fix: ~
- kind:
ImportAliasIsNotConventional:
- numpy
- np
location:
row: 5
column: 0
end_location:
row: 5
column: 12
fix: ~
- kind:
ImportAliasIsNotConventional:
- pandas
- pd
location:
row: 6
column: 0
end_location:
row: 6
column: 13
fix: ~
- kind:
ImportAliasIsNotConventional:
- seaborn
- sns
location:
row: 7
column: 0
end_location:
row: 7
column: 14
fix: ~
- kind:
ImportAliasIsNotConventional:
- altair
- alt
location:
row: 9
column: 0
end_location:
row: 9
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- matplotlib.pyplot
- plt
location:
row: 10
column: 0
end_location:
row: 10
column: 32
fix: ~
- kind:
ImportAliasIsNotConventional:
- numpy
- np
location:
row: 11
column: 0
end_location:
row: 11
column: 19
fix: ~
- kind:
ImportAliasIsNotConventional:
- pandas
- pd
location:
row: 12
column: 0
end_location:
row: 12
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- seaborn
- sns
location:
row: 13
column: 0
end_location:
row: 13
column: 22
fix: ~

View File

@@ -0,0 +1,115 @@
---
source: src/flake8_import_conventions/mod.rs
expression: checks
---
- kind:
ImportAliasIsNotConventional:
- altair
- alt
location:
row: 3
column: 0
end_location:
row: 3
column: 13
fix: ~
- kind:
ImportAliasIsNotConventional:
- matplotlib.pyplot
- plt
location:
row: 4
column: 0
end_location:
row: 4
column: 24
fix: ~
- kind:
ImportAliasIsNotConventional:
- numpy
- nmp
location:
row: 5
column: 0
end_location:
row: 5
column: 12
fix: ~
- kind:
ImportAliasIsNotConventional:
- pandas
- pd
location:
row: 6
column: 0
end_location:
row: 6
column: 13
fix: ~
- kind:
ImportAliasIsNotConventional:
- seaborn
- sns
location:
row: 7
column: 0
end_location:
row: 7
column: 14
fix: ~
- kind:
ImportAliasIsNotConventional:
- altair
- alt
location:
row: 9
column: 0
end_location:
row: 9
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- matplotlib.pyplot
- plt
location:
row: 10
column: 0
end_location:
row: 10
column: 32
fix: ~
- kind:
ImportAliasIsNotConventional:
- numpy
- nmp
location:
row: 11
column: 0
end_location:
row: 11
column: 18
fix: ~
- kind:
ImportAliasIsNotConventional:
- pandas
- pd
location:
row: 12
column: 0
end_location:
row: 12
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- seaborn
- sns
location:
row: 13
column: 0
end_location:
row: 13
column: 22
fix: ~

View File

@@ -0,0 +1,93 @@
---
source: src/flake8_import_conventions/mod.rs
expression: checks
---
- kind:
ImportAliasIsNotConventional:
- altair
- alt
location:
row: 3
column: 0
end_location:
row: 3
column: 13
fix: ~
- kind:
ImportAliasIsNotConventional:
- matplotlib.pyplot
- plt
location:
row: 4
column: 0
end_location:
row: 4
column: 24
fix: ~
- kind:
ImportAliasIsNotConventional:
- pandas
- pd
location:
row: 6
column: 0
end_location:
row: 6
column: 13
fix: ~
- kind:
ImportAliasIsNotConventional:
- seaborn
- sns
location:
row: 7
column: 0
end_location:
row: 7
column: 14
fix: ~
- kind:
ImportAliasIsNotConventional:
- altair
- alt
location:
row: 9
column: 0
end_location:
row: 9
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- matplotlib.pyplot
- plt
location:
row: 10
column: 0
end_location:
row: 10
column: 32
fix: ~
- kind:
ImportAliasIsNotConventional:
- pandas
- pd
location:
row: 12
column: 0
end_location:
row: 12
column: 21
fix: ~
- kind:
ImportAliasIsNotConventional:
- seaborn
- sns
location:
row: 13
column: 0
end_location:
row: 13
column: 22
fix: ~

View File

@@ -15,12 +15,12 @@ mod tests {
#[test]
fn ban_parent_imports() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_tidy_imports/I252.py"),
Path::new("./resources/test/fixtures/flake8_tidy_imports/TID252.py"),
&Settings {
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
ban_relative_imports: Strictness::Parents,
},
..Settings::for_rules(vec![CheckCode::I252])
..Settings::for_rules(vec![CheckCode::TID252])
},
true,
)?;
@@ -32,12 +32,12 @@ mod tests {
#[test]
fn ban_all_imports() -> Result<()> {
let mut checks = test_path(
Path::new("./resources/test/fixtures/flake8_tidy_imports/I252.py"),
Path::new("./resources/test/fixtures/flake8_tidy_imports/TID252.py"),
&Settings {
flake8_tidy_imports: flake8_tidy_imports::settings::Settings {
ban_relative_imports: Strictness::All,
},
..Settings::for_rules(vec![CheckCode::I252])
..Settings::for_rules(vec![CheckCode::TID252])
},
true,
)?;

View File

@@ -0,0 +1,19 @@
use rustpython_ast::{Constant, ExprKind, Stmt, StmtKind};
pub fn is_empty(body: &[Stmt]) -> bool {
match &body {
[] => true,
// Also allow: raise NotImplementedError, raise NotImplemented
[stmt] => match &stmt.node {
StmtKind::Pass => true,
StmtKind::Expr { value } => match &value.node {
ExprKind::Constant { value, .. } => {
matches!(value, Constant::Str(_) | Constant::Ellipsis)
}
_ => false,
},
_ => false,
},
_ => false,
}
}

View File

@@ -0,0 +1,35 @@
mod helpers;
pub mod plugins;
mod types;
#[cfg(test)]
mod tests {
use std::convert::AsRef;
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::ARG001, Path::new("ARG.py"); "ARG001")]
#[test_case(CheckCode::ARG002, Path::new("ARG.py"); "ARG002")]
#[test_case(CheckCode::ARG003, Path::new("ARG.py"); "ARG003")]
#[test_case(CheckCode::ARG004, Path::new("ARG.py"); "ARG004")]
#[test_case(CheckCode::ARG005, Path::new("ARG.py"); "ARG005")]
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_unused_arguments")
.join(path)
.as_path(),
&settings::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,184 @@
use std::iter;
use regex::Regex;
use rustc_hash::FxHashMap;
use rustpython_ast::{Arg, Arguments};
use crate::ast::function_type;
use crate::ast::function_type::FunctionType;
use crate::ast::helpers::collect_arg_names;
use crate::ast::types::{Binding, BindingKind, FunctionDef, Lambda, Scope, ScopeKind};
use crate::check_ast::Checker;
use crate::flake8_unused_arguments::helpers;
use crate::flake8_unused_arguments::types::Argumentable;
use crate::{visibility, Check};
/// Check a plain function for unused arguments.
fn function(
argumentable: &Argumentable,
args: &Arguments,
bindings: &FxHashMap<&str, Binding>,
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for arg_name in collect_arg_names(args) {
if let Some(binding) = bindings.get(arg_name) {
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Argument)
&& !dummy_variable_rgx.is_match(arg_name)
{
checks.push(Check::new(
argumentable.check_for(arg_name.to_string()),
binding.range,
));
}
}
}
checks
}
/// Check a method for unused arguments.
fn method(
argumentable: &Argumentable,
args: &Arguments,
bindings: &FxHashMap<&str, Binding>,
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for arg in args
.posonlyargs
.iter()
.chain(args.args.iter())
.skip(1)
.chain(args.kwonlyargs.iter())
.chain(iter::once::<Option<&Arg>>(args.vararg.as_deref()).flatten())
.chain(iter::once::<Option<&Arg>>(args.kwarg.as_deref()).flatten())
{
if let Some(binding) = bindings.get(&arg.node.arg.as_str()) {
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Argument)
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
{
checks.push(Check::new(
argumentable.check_for(arg.node.arg.to_string()),
binding.range,
));
}
}
}
checks
}
/// ARG001, ARG002, ARG003, ARG004, ARG005
pub fn unused_arguments(checker: &Checker, parent: &Scope, scope: &Scope) -> Vec<Check> {
match &scope.kind {
ScopeKind::Function(FunctionDef {
name,
args,
body,
decorator_list,
..
}) => {
match function_type::classify(
parent,
name,
decorator_list,
&checker.from_imports,
&checker.import_aliases,
&checker.settings.pep8_naming.classmethod_decorators,
&checker.settings.pep8_naming.staticmethod_decorators,
) {
FunctionType::Function => {
if checker
.settings
.enabled
.contains(Argumentable::Function.check_code())
{
function(
&Argumentable::Function,
args,
&scope.values,
&checker.settings.dummy_variable_rgx,
)
} else {
vec![]
}
}
FunctionType::Method => {
if checker
.settings
.enabled
.contains(Argumentable::Method.check_code())
&& !helpers::is_empty(body)
&& !visibility::is_abstract(checker, decorator_list)
&& !visibility::is_override(checker, decorator_list)
{
method(
&Argumentable::Method,
args,
&scope.values,
&checker.settings.dummy_variable_rgx,
)
} else {
vec![]
}
}
FunctionType::ClassMethod => {
if checker
.settings
.enabled
.contains(Argumentable::ClassMethod.check_code())
&& !helpers::is_empty(body)
&& !visibility::is_abstract(checker, decorator_list)
&& !visibility::is_override(checker, decorator_list)
{
method(
&Argumentable::ClassMethod,
args,
&scope.values,
&checker.settings.dummy_variable_rgx,
)
} else {
vec![]
}
}
FunctionType::StaticMethod => {
if checker
.settings
.enabled
.contains(Argumentable::StaticMethod.check_code())
&& !helpers::is_empty(body)
&& !visibility::is_abstract(checker, decorator_list)
&& !visibility::is_override(checker, decorator_list)
{
function(
&Argumentable::StaticMethod,
args,
&scope.values,
&checker.settings.dummy_variable_rgx,
)
} else {
vec![]
}
}
}
}
ScopeKind::Lambda(Lambda { args, .. }) => {
if checker
.settings
.enabled
.contains(Argumentable::Lambda.check_code())
{
function(
&Argumentable::Lambda,
args,
&scope.values,
&checker.settings.dummy_variable_rgx,
)
} else {
vec![]
}
}
_ => unreachable!("Expected ScopeKind::Function | ScopeKind::Lambda"),
}
}

View File

@@ -0,0 +1,77 @@
---
source: src/flake8_unused_arguments/mod.rs
expression: checks
---
- kind:
UnusedFunctionArgument: self
location:
row: 8
column: 6
end_location:
row: 8
column: 10
fix: ~
- kind:
UnusedFunctionArgument: x
location:
row: 8
column: 12
end_location:
row: 8
column: 13
fix: ~
- kind:
UnusedFunctionArgument: cls
location:
row: 12
column: 6
end_location:
row: 12
column: 9
fix: ~
- kind:
UnusedFunctionArgument: x
location:
row: 12
column: 11
end_location:
row: 12
column: 12
fix: ~
- kind:
UnusedFunctionArgument: self
location:
row: 16
column: 6
end_location:
row: 16
column: 10
fix: ~
- kind:
UnusedFunctionArgument: x
location:
row: 16
column: 12
end_location:
row: 16
column: 13
fix: ~
- kind:
UnusedFunctionArgument: cls
location:
row: 20
column: 6
end_location:
row: 20
column: 9
fix: ~
- kind:
UnusedFunctionArgument: x
location:
row: 20
column: 11
end_location:
row: 20
column: 12
fix: ~

View File

@@ -0,0 +1,32 @@
---
source: src/flake8_unused_arguments/mod.rs
expression: checks
---
- kind:
UnusedMethodArgument: x
location:
row: 34
column: 16
end_location:
row: 34
column: 17
fix: ~
- kind:
UnusedMethodArgument: x
location:
row: 37
column: 19
end_location:
row: 37
column: 20
fix: ~
- kind:
UnusedMethodArgument: x
location:
row: 40
column: 15
end_location:
row: 40
column: 16
fix: ~

View File

@@ -0,0 +1,14 @@
---
source: src/flake8_unused_arguments/mod.rs
expression: checks
---
- kind:
UnusedClassMethodArgument: x
location:
row: 44
column: 15
end_location:
row: 44
column: 16
fix: ~

View File

@@ -0,0 +1,32 @@
---
source: src/flake8_unused_arguments/mod.rs
expression: checks
---
- kind:
UnusedStaticMethodArgument: cls
location:
row: 48
column: 10
end_location:
row: 48
column: 13
fix: ~
- kind:
UnusedStaticMethodArgument: x
location:
row: 48
column: 15
end_location:
row: 48
column: 16
fix: ~
- kind:
UnusedStaticMethodArgument: x
location:
row: 52
column: 10
end_location:
row: 52
column: 11
fix: ~

View File

@@ -0,0 +1,14 @@
---
source: src/flake8_unused_arguments/mod.rs
expression: checks
---
- kind:
UnusedLambdaArgument: x
location:
row: 27
column: 7
end_location:
row: 27
column: 8
fix: ~

View File

@@ -0,0 +1,32 @@
use crate::checks::{CheckCode, CheckKind};
/// An AST node that can contain arguments.
pub enum Argumentable {
Function,
Method,
ClassMethod,
StaticMethod,
Lambda,
}
impl Argumentable {
pub fn check_for(&self, name: String) -> CheckKind {
match self {
Argumentable::Function => CheckKind::UnusedFunctionArgument(name),
Argumentable::Method => CheckKind::UnusedMethodArgument(name),
Argumentable::ClassMethod => CheckKind::UnusedClassMethodArgument(name),
Argumentable::StaticMethod => CheckKind::UnusedStaticMethodArgument(name),
Argumentable::Lambda => CheckKind::UnusedLambdaArgument(name),
}
}
pub fn check_code(&self) -> &CheckCode {
match self {
Argumentable::Function => &CheckCode::ARG001,
Argumentable::Method => &CheckCode::ARG002,
Argumentable::ClassMethod => &CheckCode::ARG003,
Argumentable::StaticMethod => &CheckCode::ARG004,
Argumentable::Lambda => &CheckCode::ARG005,
}
}
}

View File

@@ -1,5 +1,4 @@
use std::borrow::Cow;
use std::collections::BTreeSet;
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::{Path, PathBuf};
@@ -8,6 +7,7 @@ use anyhow::{anyhow, Result};
use globset::GlobMatcher;
use log::debug;
use path_absolutize::{path_dedot, Absolutize};
use rustc_hash::FxHashSet;
use walkdir::{DirEntry, WalkDir};
use crate::checks::CheckCode;
@@ -83,8 +83,8 @@ pub fn iter_python_files<'a>(
/// Create tree set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path<'a>(
path: &Path,
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, BTreeSet<CheckCode>)],
) -> Result<BTreeSet<&'a CheckCode>> {
pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet<CheckCode>)],
) -> Result<FxHashSet<&'a CheckCode>> {
let (file_path, file_basename) = extract_path_names(path)?;
Ok(pattern_code_pairs
.iter()

View File

@@ -559,6 +559,7 @@ mod tests {
#[test_case(Path::new("force_wrap_aliases.py"))]
#[test_case(Path::new("import_from_after_import.py"))]
#[test_case(Path::new("insert_empty_lines.py"))]
#[test_case(Path::new("insert_empty_lines.pyi"))]
#[test_case(Path::new("leading_prefix.py"))]
#[test_case(Path::new("no_reorder_within_section.py"))]
#[test_case(Path::new("no_wrap_star.py"))]

View File

@@ -0,0 +1,65 @@
---
source: src/isort/mod.rs
expression: checks
---
- kind: UnsortedImports
location:
row: 1
column: 0
end_location:
row: 3
column: 0
fix:
content: "import a\nimport b\n\n"
location:
row: 1
column: 0
end_location:
row: 3
column: 0
- kind: UnsortedImports
location:
row: 4
column: 0
end_location:
row: 6
column: 0
fix:
content: "import os\nimport sys\n\n"
location:
row: 4
column: 0
end_location:
row: 6
column: 0
- kind: UnsortedImports
location:
row: 14
column: 0
end_location:
row: 16
column: 0
fix:
content: "import os\nimport sys\n\n"
location:
row: 14
column: 0
end_location:
row: 16
column: 0
- kind: UnsortedImports
location:
row: 33
column: 0
end_location:
row: 35
column: 0
fix:
content: " import collections\n import typing\n\n"
location:
row: 33
column: 0
end_location:
row: 35
column: 0

View File

@@ -1,3 +1,5 @@
use std::path::Path;
use rustpython_ast::{
Alias, Arg, Arguments, Boolop, Cmpop, Comprehension, Constant, Excepthandler,
ExcepthandlerKind, Expr, ExprContext, Keyword, MatchCase, Operator, Pattern, Stmt, StmtKind,
@@ -20,16 +22,18 @@ pub struct Block<'a> {
}
pub struct ImportTracker<'a> {
blocks: Vec<Block<'a>>,
directives: &'a IsortDirectives,
pyi: bool,
blocks: Vec<Block<'a>>,
split_index: usize,
nested: bool,
}
impl<'a> ImportTracker<'a> {
pub fn new(directives: &'a IsortDirectives) -> Self {
pub fn new(directives: &'a IsortDirectives, path: &'a Path) -> Self {
Self {
directives,
pyi: path.extension().map_or(false, |ext| ext == "pyi"),
blocks: vec![Block::default()],
split_index: 0,
nested: false,
@@ -41,6 +45,34 @@ impl<'a> ImportTracker<'a> {
self.blocks[index].imports.push(stmt);
}
fn trailer_for(&self, stmt: &'a Stmt) -> Option<Trailer> {
if self.pyi {
// Black treats interface files differently, limiting to one newline
// (`Trailing::Sibling`), and avoiding inserting any newlines in nested function
// blocks.
if self.nested
&& matches!(
stmt.node,
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. }
)
{
None
} else {
Some(Trailer::Sibling)
}
} else if self.nested {
Some(Trailer::Sibling)
} else {
Some(match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
Trailer::FunctionDef
}
StmtKind::ClassDef { .. } => Trailer::ClassDef,
_ => Trailer::Sibling,
})
}
}
fn finalize(&mut self, trailer: Option<Trailer>) {
let index = self.blocks.len() - 1;
if !self.blocks[index].imports.is_empty() {
@@ -62,17 +94,7 @@ where
// Track manual splits.
while self.split_index < self.directives.splits.len() {
if stmt.location.row() >= self.directives.splits[self.split_index] {
self.finalize(Some(if self.nested {
Trailer::Sibling
} else {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
Trailer::FunctionDef
}
StmtKind::ClassDef { .. } => Trailer::ClassDef,
_ => Trailer::Sibling,
}
}));
self.finalize(self.trailer_for(stmt));
self.split_index += 1;
} else {
break;
@@ -87,17 +109,7 @@ where
{
self.track_import(stmt);
} else {
self.finalize(Some(if self.nested {
Trailer::Sibling
} else {
match &stmt.node {
StmtKind::FunctionDef { .. } | StmtKind::AsyncFunctionDef { .. } => {
Trailer::FunctionDef
}
StmtKind::ClassDef { .. } => Trailer::ClassDef,
_ => Trailer::Sibling,
}
}));
self.finalize(self.trailer_for(stmt));
}
// Track scope.

View File

@@ -49,10 +49,12 @@ pub mod flake8_bugbear;
mod flake8_builtins;
mod flake8_comprehensions;
mod flake8_debugger;
mod flake8_import_conventions;
mod flake8_print;
pub mod flake8_quotes;
mod flake8_return;
pub mod flake8_tidy_imports;
mod flake8_unused_arguments;
pub mod fs;
mod isort;
mod lex;

View File

@@ -92,6 +92,7 @@ pub(crate) fn check_path(
&directives.isort,
settings,
autofix,
path,
));
}
}
@@ -133,7 +134,7 @@ pub(crate) fn check_path(
Ok(checks)
}
const MAX_ITERATIONS: usize = 1;
const MAX_ITERATIONS: usize = 100;
/// Lint the source code at the given `Path`.
pub fn lint_path(

View File

@@ -392,7 +392,7 @@ fn inner_main() -> Result<ExitCode> {
// unless we're writing fixes via stdin (in which case, the transformed
// source code goes to stdout).
if !(is_stdin && matches!(autofix, fixer::Mode::Apply)) {
printer.write_once(&diagnostics)?;
printer.write_once(&diagnostics, &autofix)?;
}
// Check for updates if we're in a non-silent log level.

View File

@@ -24,8 +24,7 @@ pub struct Settings {
}
impl Settings {
#[allow(clippy::needless_pass_by_value)]
pub fn from_options(options: Options) -> Self {
pub fn from_options(options: &Options) -> Self {
Self {
max_complexity: options.max_complexity.unwrap_or_default(),
}

View File

@@ -1,4 +1,3 @@
use std::collections::{BTreeMap, BTreeSet};
use std::fs;
use std::path::Path;
@@ -7,8 +6,9 @@ use itertools::Itertools;
use nohash_hasher::IntMap;
use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::checks::{Check, CheckCode, REDIRECTS};
use crate::checks::{Check, CheckCode, CODE_REDIRECTS};
static NO_QA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
@@ -70,7 +70,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
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) {
if let Some(candidate) = CODE_REDIRECTS.get(candidate) {
needle == candidate.as_ref()
} else {
&needle == candidate
@@ -83,7 +83,7 @@ pub fn add_noqa(
checks: &[Check],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
external: &BTreeSet<String>,
external: &FxHashSet<String>,
) -> Result<usize> {
let (count, output) = add_noqa_inner(checks, contents, noqa_line_for, external);
fs::write(path, output)?;
@@ -94,16 +94,16 @@ fn add_noqa_inner(
checks: &[Check],
contents: &str,
noqa_line_for: &IntMap<usize, usize>,
external: &BTreeSet<String>,
external: &FxHashSet<String>,
) -> (usize, String) {
let mut matches_by_line: BTreeMap<usize, BTreeSet<&CheckCode>> = BTreeMap::new();
let mut matches_by_line: FxHashMap<usize, FxHashSet<&CheckCode>> = FxHashMap::default();
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();
let mut codes: FxHashSet<&CheckCode> = FxHashSet::default();
for check in checks {
if check.location.row() == lineno + 1 {
codes.insert(check.kind.code());
@@ -117,7 +117,7 @@ fn add_noqa_inner(
if !codes.is_empty() {
let matches = matches_by_line.entry(noqa_lineno).or_default();
matches.append(&mut codes);
matches.extend(codes);
}
}
@@ -199,9 +199,9 @@ fn add_noqa_inner(
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use nohash_hasher::IntMap;
use rustc_hash::FxHashSet;
use rustpython_parser::ast::Location;
use crate::ast::types::Range;
@@ -227,7 +227,7 @@ mod tests {
let checks = vec![];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let external = BTreeSet::default();
let external = FxHashSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 0);
assert_eq!(output.trim(), contents.trim());
@@ -241,7 +241,7 @@ mod tests {
)];
let contents = "x = 1";
let noqa_line_for = IntMap::default();
let external = BTreeSet::default();
let external = FxHashSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: F841".trim());
@@ -264,7 +264,7 @@ mod tests {
];
let contents = "x = 1 # noqa: E741";
let noqa_line_for = IntMap::default();
let external = BTreeSet::default();
let external = FxHashSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());
@@ -287,7 +287,7 @@ mod tests {
];
let contents = "x = 1 # noqa";
let noqa_line_for = IntMap::default();
let external = BTreeSet::default();
let external = FxHashSet::default();
let (count, output) = add_noqa_inner(&checks, contents, &noqa_line_for, &external);
assert_eq!(count, 1);
assert_eq!(output.trim(), "x = 1 # noqa: E741, F841".trim());

View File

@@ -1,10 +1,10 @@
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
use crate::ast::function_type;
use crate::ast::types::{Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
use crate::pep8_naming::helpers;
use crate::pep8_naming::helpers::FunctionType;
use crate::pep8_naming::settings::Settings;
use crate::python::string::{self};
@@ -58,15 +58,16 @@ pub fn invalid_first_argument_name_for_class_method(
settings: &Settings,
) -> Option<Check> {
if !matches!(
helpers::function_type(
function_type::classify(
scope,
name,
decorator_list,
from_imports,
import_aliases,
settings,
&settings.classmethod_decorators,
&settings.staticmethod_decorators,
),
FunctionType::ClassMethod
function_type::FunctionType::ClassMethod
) {
return None;
}
@@ -99,15 +100,16 @@ pub fn invalid_first_argument_name_for_method(
settings: &Settings,
) -> Option<Check> {
if !matches!(
helpers::function_type(
function_type::classify(
scope,
name,
decorator_list,
from_imports,
import_aliases,
settings,
&settings.classmethod_decorators,
&settings.staticmethod_decorators,
),
FunctionType::Method
function_type::FunctionType::Method
) {
return None;
}

View File

@@ -1,71 +1,10 @@
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Expr, Stmt, StmtKind};
use rustpython_ast::{Stmt, StmtKind};
use crate::ast::helpers::{
collect_call_paths, dealias_call_path, match_call_path, to_module_and_member,
};
use crate::ast::types::{Scope, ScopeKind};
use crate::pep8_naming::settings::Settings;
use crate::ast::helpers::{collect_call_paths, match_call_path};
use crate::python::string::{is_lower, is_upper};
const CLASS_METHODS: [&str; 3] = ["__new__", "__init_subclass__", "__class_getitem__"];
const METACLASS_BASES: [(&str, &str); 2] = [("", "type"), ("abc", "ABCMeta")];
pub enum FunctionType {
Function,
Method,
ClassMethod,
StaticMethod,
}
/// Classify a function based on its scope, name, and decorators.
pub fn function_type(
scope: &Scope,
name: &str,
decorator_list: &[Expr],
from_imports: &FxHashMap<&str, FxHashSet<&str>>,
import_aliases: &FxHashMap<&str, &str>,
settings: &Settings,
) -> FunctionType {
let ScopeKind::Class(scope) = &scope.kind else {
return FunctionType::Function;
};
// Special-case class method, like `__new__`.
if CLASS_METHODS.contains(&name)
|| scope.bases.iter().any(|expr| {
// The class itself extends a known metaclass, so all methods are class methods.
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
METACLASS_BASES
.iter()
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
})
|| decorator_list.iter().any(|expr| {
// The method is decorated with a class method decorator (like `@classmethod`).
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
settings.classmethod_decorators.iter().any(|decorator| {
let (module, member) = to_module_and_member(decorator);
match_call_path(&call_path, module, member, from_imports)
})
})
{
FunctionType::ClassMethod
} else if decorator_list.iter().any(|expr| {
// The method is decorated with a static method decorator (like
// `@staticmethod`).
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
settings.staticmethod_decorators.iter().any(|decorator| {
let (module, member) = to_module_and_member(decorator);
match_call_path(&call_path, module, member, from_imports)
})
}) {
FunctionType::StaticMethod
} else {
// It's an instance method.
FunctionType::Method
}
}
pub fn is_camelcase(name: &str) -> bool {
!is_lower(name) && !is_upper(name) && !name.contains('_')
}

View File

@@ -9,7 +9,7 @@ use itertools::iterate;
use rustpython_parser::ast::Location;
use serde::Serialize;
use crate::autofix::Fix;
use crate::autofix::{fixer, Fix};
use crate::checks::CheckCode;
use crate::fs::relativize_path;
use crate::linter::Diagnostics;
@@ -57,15 +57,15 @@ impl<'a> Printer<'a> {
}
}
fn post_text(&self, num_fixable: usize) {
fn post_text(&self, num_fixable: usize, autofix: &fixer::Mode) {
if self.log_level >= &LogLevel::Default {
if num_fixable > 0 {
if num_fixable > 0 && !matches!(autofix, fixer::Mode::Apply) {
println!("{num_fixable} potentially fixable with the --fix option.");
}
}
}
pub fn write_once(&self, diagnostics: &Diagnostics) -> Result<()> {
pub fn write_once(&self, diagnostics: &Diagnostics, autofix: &fixer::Mode) -> Result<()> {
if matches!(self.log_level, LogLevel::Silent) {
return Ok(());
}
@@ -147,7 +147,7 @@ impl<'a> Printer<'a> {
print_message(message);
}
self.post_text(num_fixable);
self.post_text(num_fixable, autofix);
}
SerializationFormat::Grouped => {
self.pre_text(diagnostics);
@@ -190,7 +190,7 @@ impl<'a> Printer<'a> {
println!();
}
self.post_text(num_fixable);
self.post_text(num_fixable, autofix);
}
SerializationFormat::Github => {
self.pre_text(diagnostics);

View File

@@ -1,11 +1,65 @@
use itertools::izip;
use rustpython_ast::Location;
use rustpython_ast::{Location, Stmt, StmtKind};
use rustpython_parser::ast::{Cmpop, Expr, ExprKind};
use crate::ast::types::Range;
use crate::checks::{Check, CheckKind};
use crate::source_code_locator::SourceCodeLocator;
/// E721
pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for (op, right) in izip!(ops, comparators) {
if !matches!(op, Cmpop::Is | Cmpop::IsNot | Cmpop::Eq | Cmpop::NotEq) {
continue;
}
match &right.node {
ExprKind::Call { func, args, .. } => {
if let ExprKind::Name { id, .. } = &func.node {
// Ex) type(False)
if id == "type" {
if let Some(arg) = args.first() {
// Allow comparison for types which are not obvious.
if !matches!(arg.node, ExprKind::Name { .. }) {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
}
}
}
ExprKind::Attribute { value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
// Ex) types.IntType
if id == "types" {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
}
_ => {}
}
}
checks
}
/// E722
pub fn do_not_use_bare_except(
type_: Option<&Expr>,
body: &[Stmt],
location: Range,
) -> Option<Check> {
if type_.is_none()
&& !body
.iter()
.any(|stmt| matches!(stmt.node, StmtKind::Raise { exc: None, .. }))
{
Some(Check::new(CheckKind::DoNotUseBareExcept, location))
} else {
None
}
}
fn is_ambiguous_name(name: &str) -> bool {
name == "l" || name == "I" || name == "O"
}
@@ -46,43 +100,6 @@ pub fn ambiguous_function_name(name: &str, location: Range) -> Option<Check> {
}
}
/// E721
pub fn type_comparison(ops: &[Cmpop], comparators: &[Expr], location: Range) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for (op, right) in izip!(ops, comparators) {
if !matches!(op, Cmpop::Is | Cmpop::IsNot | Cmpop::Eq | Cmpop::NotEq) {
continue;
}
match &right.node {
ExprKind::Call { func, args, .. } => {
if let ExprKind::Name { id, .. } = &func.node {
// Ex) type(False)
if id == "type" {
if let Some(arg) = args.first() {
// Allow comparison for types which are not obvious.
if !matches!(arg.node, ExprKind::Name { .. }) {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
}
}
}
ExprKind::Attribute { value, .. } => {
if let ExprKind::Name { id, .. } = &value.node {
// Ex) types.IntType
if id == "types" {
checks.push(Check::new(CheckKind::TypeComparison, location));
}
}
}
_ => {}
}
}
checks
}
// See: https://docs.python.org/3/reference/lexical_analysis.html#string-and-bytes-literals
const VALID_ESCAPE_SEQUENCES: &[char; 23] = &[
'\n', '\\', '\'', '"', 'a', 'b', 'f', 'n', 'r', 't', 'v', '0', '1', '2', '3', '4', '5', '6',

View File

@@ -1,5 +1,3 @@
use std::collections::BTreeSet;
use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
@@ -7,8 +5,8 @@ use rustc_hash::FxHashSet;
use rustpython_ast::{Constant, ExprKind, Location, StmtKind};
use crate::ast::types::Range;
use crate::ast::whitespace;
use crate::ast::whitespace::LinesWithTrailingNewline;
use crate::ast::{cast, whitespace};
use crate::autofix::Fix;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckCode, CheckKind};
@@ -77,7 +75,7 @@ pub fn not_missing(
false
}
DefinitionKind::Function(stmt) | DefinitionKind::NestedFunction(stmt) => {
if is_overload(checker, stmt) {
if is_overload(checker, cast::decorator_list(stmt)) {
true
} else {
if checker.settings.enabled.contains(&CheckCode::D103) {
@@ -90,7 +88,9 @@ pub fn not_missing(
}
}
DefinitionKind::Method(stmt) => {
if is_overload(checker, stmt) || is_override(checker, stmt) {
if is_overload(checker, cast::decorator_list(stmt))
|| is_override(checker, cast::decorator_list(stmt))
{
true
} else if is_magic(stmt) {
if checker.settings.enabled.contains(&CheckCode::D105) {
@@ -916,7 +916,7 @@ pub fn if_needed(checker: &mut Checker, definition: &Definition) {
) = definition.kind else {
return
};
if !is_overload(checker, stmt) {
if !is_overload(checker, cast::decorator_list(stmt)) {
return;
}
checker.add_check(Check::new(
@@ -1400,7 +1400,7 @@ fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args:
};
// Look for arguments that weren't included in the docstring.
let mut missing_arg_names: BTreeSet<String> = BTreeSet::default();
let mut missing_arg_names: FxHashSet<String> = FxHashSet::default();
for arg in arguments
.args
.iter()
@@ -1410,7 +1410,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(checker, parent),
&& !is_staticmethod(checker, cast::decorator_list(parent)),
),
)
{

View File

@@ -25,29 +25,30 @@ impl TryFrom<&str> for CFormatSummary {
let mut keywords = FxHashSet::default();
for format_part in format_string.parts {
if let CFormatPart::Spec(CFormatSpec {
let CFormatPart::Spec(CFormatSpec {
mapping_key,
min_field_width,
precision,
..
}) = format_part.1
}) = format_part.1 else
{
match mapping_key {
Some(k) => {
keywords.insert(k);
}
None => {
num_positional += 1;
}
};
if min_field_width == Some(CFormatQuantity::FromValuesTuple) {
num_positional += 1;
starred = true;
continue;
};
match mapping_key {
Some(k) => {
keywords.insert(k);
}
if precision == Some(CFormatQuantity::FromValuesTuple) {
None => {
num_positional += 1;
starred = true;
}
};
if min_field_width == Some(CFormatQuantity::FromValuesTuple) {
num_positional += 1;
starred = true;
}
if precision == Some(CFormatQuantity::FromValuesTuple) {
num_positional += 1;
starred = true;
}
}

View File

@@ -7,7 +7,7 @@ use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
};
use crate::ast::types::{BindingKind, FunctionScope, Range, Scope, ScopeKind};
use crate::ast::types::{BindingKind, Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
use crate::pyflakes::cformat::CFormatSummary;
use crate::pyflakes::format::FormatSummary;
@@ -77,43 +77,39 @@ pub(crate) fn percent_format_extra_named_arguments(
if summary.num_positional > 0 {
return None;
}
if let ExprKind::Dict { keys, values } = &right.node {
if values.len() > keys.len() {
return None; // contains **x splat
}
let missing: Vec<&String> = keys
.iter()
.filter_map(|k| match &k.node {
// We can only check that string literals exist
ExprKind::Constant {
value: Constant::Str(value),
..
} => {
if summary.keywords.contains(value) {
None
} else {
Some(value)
}
}
_ => None,
})
.collect();
if missing.is_empty() {
None
} else {
Some(Check::new(
CheckKind::PercentFormatExtraNamedArguments(
missing.iter().map(|&s| s.clone()).collect(),
),
location,
))
}
} else {
None
let ExprKind::Dict { keys, values } = &right.node else {
return None;
};
if values.len() > keys.len() {
return None; // contains **x splat
}
let missing: Vec<&String> = keys
.iter()
.filter_map(|k| match &k.node {
// We can only check that string literals exist
ExprKind::Constant {
value: Constant::Str(value),
..
} => {
if summary.keywords.contains(value) {
None
} else {
Some(value)
}
}
_ => None,
})
.collect();
if missing.is_empty() {
return None;
}
Some(Check::new(
CheckKind::PercentFormatExtraNamedArguments(missing.iter().map(|&s| s.clone()).collect()),
location,
))
}
/// F505
@@ -395,13 +391,7 @@ pub fn undefined_local(scopes: &[&Scope], name: &str) -> Option<Check> {
pub fn unused_variables(scope: &Scope, dummy_variable_rgx: &Regex) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
if matches!(
scope.kind,
ScopeKind::Function(FunctionScope {
uses_locals: true,
..
})
) {
if scope.uses_locals && matches!(scope.kind, ScopeKind::Function(..)) {
return checks;
}

View File

@@ -43,30 +43,31 @@ impl TryFrom<&str> for FormatSummary {
let mut keywords = FxHashSet::default();
for format_part in format_string.format_parts {
if let FormatPart::Field {
let FormatPart::Field {
field_name,
format_spec,
..
} = format_part
{
} = format_part else {
continue;
};
let parsed = FieldName::parse(&field_name)?;
match parsed.field_type {
FieldType::Auto => autos.insert(autos.len()),
FieldType::Index(i) => indexes.insert(i),
FieldType::Keyword(k) => keywords.insert(k),
};
let nested = FormatString::from_str(&format_spec)?;
for nested_part in nested.format_parts {
let FormatPart::Field { field_name, .. } = nested_part else {
continue;
};
let parsed = FieldName::parse(&field_name)?;
match parsed.field_type {
FieldType::Auto => autos.insert(autos.len()),
FieldType::Index(i) => indexes.insert(i),
FieldType::Keyword(k) => keywords.insert(k),
};
let nested = FormatString::from_str(&format_spec)?;
for nested_part in nested.format_parts {
if let FormatPart::Field { field_name, .. } = nested_part {
let parsed = FieldName::parse(&field_name)?;
match parsed.field_type {
FieldType::Auto => autos.insert(autos.len()),
FieldType::Index(i) => indexes.insert(i),
FieldType::Keyword(k) => keywords.insert(k),
};
}
}
}
}

View File

@@ -6,19 +6,22 @@ use crate::checks::{Check, CheckKind};
/// F633
pub fn invalid_print_syntax(checker: &mut Checker, left: &Expr) {
if let ExprKind::Name { id, .. } = &left.node {
if id == "print" {
let scope = checker.current_scope();
if let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("print")
{
checker.add_check(Check::new(
CheckKind::InvalidPrintSyntax,
Range::from_located(left),
));
}
}
let ExprKind::Name { id, .. } = &left.node else {
return;
};
if id != "print" {
return;
}
let scope = checker.current_scope();
let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("print") else
{
return;
};
checker.add_check(Check::new(
CheckKind::InvalidPrintSyntax,
Range::from_located(left),
));
}

View File

@@ -26,15 +26,16 @@ fn match_not_implemented(expr: &Expr) -> Option<&Expr> {
/// F901
pub fn raise_not_implemented(checker: &mut Checker, expr: &Expr) {
if let Some(expr) = match_not_implemented(expr) {
let mut check = Check::new(CheckKind::RaiseNotImplemented, Range::from_located(expr));
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
"NotImplementedError".to_string(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
let Some(expr) = match_not_implemented(expr) else {
return;
};
let mut check = Check::new(CheckKind::RaiseNotImplemented, Range::from_located(expr));
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
"NotImplementedError".to_string(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}

View File

@@ -5,11 +5,14 @@ use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
pub fn no_eval(checker: &mut Checker, func: &Expr) {
if let ExprKind::Name { id, .. } = &func.node {
if id == "eval" {
if checker.is_builtin("eval") {
checker.add_check(Check::new(CheckKind::NoEval, Range::from_located(func)));
}
}
let ExprKind::Name { id, .. } = &func.node else {
return;
};
if id != "eval" {
return;
}
if !checker.is_builtin("eval") {
return;
}
checker.add_check(Check::new(CheckKind::NoEval, Range::from_located(func)));
}

View File

@@ -1,6 +1,6 @@
use rustpython_ast::Expr;
use crate::ast::types::{FunctionScope, Range, ScopeKind};
use crate::ast::types::{FunctionDef, Range, ScopeKind};
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
@@ -10,7 +10,7 @@ pub fn await_outside_async(checker: &mut Checker, expr: &Expr) {
if !checker
.current_scopes()
.find_map(|scope| {
if let ScopeKind::Function(FunctionScope { async_, .. }) = &scope.kind {
if let ScopeKind::Function(FunctionDef { async_, .. }) = &scope.kind {
Some(*async_)
} else {
None

View File

@@ -1,52 +0,0 @@
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Boolop, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLR1701
pub fn consider_merging_isinstance(
checker: &mut Checker,
expr: &Expr,
op: &Boolop,
values: &[Expr],
) {
if !matches!(op, Boolop::Or) || !checker.is_builtin("isinstance") {
return;
}
let mut obj_to_types: FxHashMap<String, (usize, FxHashSet<String>)> = FxHashMap::default();
for value in values {
if let ExprKind::Call { func, args, .. } = &value.node {
if matches!(&func.node, ExprKind::Name { id, .. } if id == "isinstance") {
if let [obj, types] = &args[..] {
let (num_calls, matches) = obj_to_types
.entry(obj.to_string())
.or_insert_with(|| (0, FxHashSet::default()));
*num_calls += 1;
matches.extend(match &types.node {
ExprKind::Tuple { elts, .. } => {
elts.iter().map(std::string::ToString::to_string).collect()
}
_ => {
vec![types.to_string()]
}
});
}
}
}
}
for (obj, (num_calls, types)) in obj_to_types {
if num_calls > 1 && types.len() > 1 {
checker.add_check(Check::new(
CheckKind::ConsiderMergingIsinstance(obj, types.into_iter().sorted().collect()),
Range::from_located(expr),
));
}
}
}

View File

@@ -1,20 +0,0 @@
use rustpython_ast::Alias;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLR0402
pub fn consider_using_from_import(checker: &mut Checker, alias: &Alias) {
if let Some(asname) = &alias.node.asname {
if let Some((module, name)) = alias.node.name.rsplit_once('.') {
if name == asname {
checker.add_check(Check::new(
CheckKind::ConsiderUsingFromImport(module.to_string(), name.to_string()),
Range::from_located(alias),
));
}
}
}
}

View File

@@ -0,0 +1,50 @@
use itertools::Itertools;
use rustc_hash::{FxHashMap, FxHashSet};
use rustpython_ast::{Boolop, Expr, ExprKind};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLR1701
pub fn merge_isinstance(checker: &mut Checker, expr: &Expr, op: &Boolop, values: &[Expr]) {
if !matches!(op, Boolop::Or) || !checker.is_builtin("isinstance") {
return;
}
let mut obj_to_types: FxHashMap<String, (usize, FxHashSet<String>)> = FxHashMap::default();
for value in values {
let ExprKind::Call { func, args, .. } = &value.node else {
continue;
};
if !matches!(&func.node, ExprKind::Name { id, .. } if id == "isinstance") {
continue;
}
let [obj, types] = &args[..] else {
continue;
};
let (num_calls, matches) = obj_to_types
.entry(obj.to_string())
.or_insert_with(|| (0, FxHashSet::default()));
*num_calls += 1;
matches.extend(match &types.node {
ExprKind::Tuple { elts, .. } => {
elts.iter().map(std::string::ToString::to_string).collect()
}
_ => {
vec![types.to_string()]
}
});
}
for (obj, (num_calls, types)) in obj_to_types {
if num_calls > 1 && types.len() > 1 {
checker.add_check(Check::new(
CheckKind::ConsiderMergingIsinstance(obj, types.into_iter().sorted().collect()),
Range::from_located(expr),
));
}
}
}

View File

@@ -14,35 +14,43 @@ pub fn misplaced_comparison_constant(
ops: &[Cmpop],
comparators: &[Expr],
) {
if let ([op], [right]) = (ops, comparators) {
if matches!(
op,
Cmpop::Eq | Cmpop::NotEq | Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE,
) && matches!(&left.node, &ExprKind::Constant { .. })
&& !matches!(&right.node, &ExprKind::Constant { .. })
{
let reversed_op = match op {
Cmpop::Eq => "==",
Cmpop::NotEq => "!=",
Cmpop::Lt => ">",
Cmpop::LtE => ">=",
Cmpop::Gt => "<",
Cmpop::GtE => "<=",
_ => unreachable!("Expected comparison operator"),
};
let suggestion = format!("{right} {reversed_op} {left}");
let mut check = Check::new(
CheckKind::MisplacedComparisonConstant(suggestion.clone()),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
suggestion,
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
let ([op], [right]) = (ops, comparators) else {
return;
};
if !matches!(
op,
Cmpop::Eq | Cmpop::NotEq | Cmpop::Lt | Cmpop::LtE | Cmpop::Gt | Cmpop::GtE,
) {
return;
}
if !matches!(&left.node, &ExprKind::Constant { .. }) {
return;
}
if matches!(&right.node, &ExprKind::Constant { .. }) {
return;
}
let reversed_op = match op {
Cmpop::Eq => "==",
Cmpop::NotEq => "!=",
Cmpop::Lt => ">",
Cmpop::LtE => ">=",
Cmpop::Gt => "<",
Cmpop::GtE => "<=",
_ => unreachable!("Expected comparison operator"),
};
let suggestion = format!("{right} {reversed_op} {left}");
let mut check = Check::new(
CheckKind::MisplacedComparisonConstant(suggestion.clone()),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
suggestion,
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}

View File

@@ -1,19 +1,19 @@
pub use await_outside_async::await_outside_async;
pub use consider_merging_isinstance::consider_merging_isinstance;
pub use consider_using_from_import::consider_using_from_import;
pub use consider_using_sys_exit::consider_using_sys_exit;
pub use merge_isinstance::merge_isinstance;
pub use misplaced_comparison_constant::misplaced_comparison_constant;
pub use property_with_parameters::property_with_parameters;
pub use unnecessary_direct_lambda_call::unnecessary_direct_lambda_call;
pub use use_from_import::use_from_import;
pub use use_sys_exit::use_sys_exit;
pub use useless_else_on_loop::useless_else_on_loop;
pub use useless_import_alias::useless_import_alias;
mod await_outside_async;
mod consider_merging_isinstance;
mod consider_using_from_import;
mod consider_using_sys_exit;
mod merge_isinstance;
mod misplaced_comparison_constant;
mod property_with_parameters;
mod unnecessary_direct_lambda_call;
mod use_from_import;
mod use_sys_exit;
mod useless_else_on_loop;
mod useless_import_alias;

View File

@@ -12,23 +12,24 @@ pub fn property_with_parameters(
decorator_list: &[Expr],
args: &Arguments,
) {
if decorator_list
if !decorator_list
.iter()
.any(|d| matches!(&d.node, ExprKind::Name { id, .. } if id == "property"))
{
if checker.is_builtin("property")
&& args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
.count()
> 1
{
checker.add_check(Check::new(
CheckKind::PropertyWithParameters,
Range::from_located(stmt),
));
}
return;
}
if checker.is_builtin("property")
&& args
.args
.iter()
.chain(args.posonlyargs.iter())
.chain(args.kwonlyargs.iter())
.count()
> 1
{
checker.add_check(Check::new(
CheckKind::PropertyWithParameters,
Range::from_located(stmt),
));
}
}

View File

@@ -0,0 +1,23 @@
use rustpython_ast::Alias;
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::CheckKind;
use crate::Check;
/// PLR0402
pub fn use_from_import(checker: &mut Checker, alias: &Alias) {
let Some(asname) = &alias.node.asname else {
return;
};
let Some((module, name)) = alias.node.name.rsplit_once('.') else {
return;
};
if name != asname {
return;
}
checker.add_check(Check::new(
CheckKind::ConsiderUsingFromImport(module.to_string(), name.to_string()),
Range::from_located(alias),
));
}

View File

@@ -60,28 +60,33 @@ fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -
}
/// RUF004
pub fn consider_using_sys_exit(checker: &mut Checker, func: &Expr) {
if let ExprKind::Name { id, .. } = &func.node {
for name in ["exit", "quit"] {
if id == name {
if !(name == "exit" && is_module_star_imported(checker, "sys"))
&& checker.is_builtin(name)
{
let mut check =
Check::new(CheckKind::ConsiderUsingSysExit, Range::from_located(func));
if checker.patch(check.kind.code()) {
if let Some(content) = get_member_import_name_alias(checker, "sys", "exit")
{
check.amend(Fix::replacement(
content,
func.location,
func.end_location.unwrap(),
));
}
}
checker.add_check(check);
}
pub fn use_sys_exit(checker: &mut Checker, func: &Expr) {
let ExprKind::Name { id, .. } = &func.node else {
return;
};
for name in ["exit", "quit"] {
if id != name {
continue;
}
if name == "exit" && is_module_star_imported(checker, "sys") {
continue;
}
if !checker.is_builtin(name) {
continue;
}
let mut check = Check::new(
CheckKind::UseSysExit(name.to_string()),
Range::from_located(func),
);
if checker.patch(check.kind.code()) {
if let Some(content) = get_member_import_name_alias(checker, "sys", "exit") {
check.amend(Fix::replacement(
content,
func.location,
func.end_location.unwrap(),
));
}
}
checker.add_check(check);
}
}

View File

@@ -8,17 +8,23 @@ use crate::Check;
/// PLC0414
pub fn useless_import_alias(checker: &mut Checker, alias: &Alias) {
if let Some(asname) = &alias.node.asname {
if !alias.node.name.contains('.') && &alias.node.name == asname {
let mut check = Check::new(CheckKind::UselessImportAlias, Range::from_located(alias));
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
asname.to_string(),
alias.location,
alias.end_location.unwrap(),
));
}
checker.add_check(check);
}
let Some(asname) = &alias.node.asname else {
return;
};
if alias.node.name.contains('.') {
return;
}
if &alias.node.name != asname {
return;
}
let mut check = Check::new(CheckKind::UselessImportAlias, Range::from_located(alias));
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
asname.to_string(),
alias.location,
alias.end_location.unwrap(),
));
}
checker.add_check(check);
}

View File

@@ -2,7 +2,8 @@
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 1
column: 0
@@ -10,7 +11,8 @@ expression: checks
row: 1
column: 4
fix: ~
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 2
column: 0
@@ -18,7 +20,8 @@ expression: checks
row: 2
column: 4
fix: ~
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 6
column: 4
@@ -26,7 +29,8 @@ expression: checks
row: 6
column: 8
fix: ~
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 7
column: 4

View File

@@ -2,7 +2,8 @@
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 3
column: 0
@@ -17,7 +18,8 @@ expression: checks
end_location:
row: 3
column: 4
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 4
column: 0
@@ -32,7 +34,8 @@ expression: checks
end_location:
row: 4
column: 4
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 8
column: 4
@@ -47,7 +50,8 @@ expression: checks
end_location:
row: 8
column: 8
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 9
column: 4

View File

@@ -2,7 +2,8 @@
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 3
column: 0
@@ -17,7 +18,8 @@ expression: checks
end_location:
row: 3
column: 4
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 4
column: 0
@@ -32,7 +34,8 @@ expression: checks
end_location:
row: 4
column: 4
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 8
column: 4
@@ -47,7 +50,8 @@ expression: checks
end_location:
row: 8
column: 8
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 9
column: 4

View File

@@ -2,7 +2,8 @@
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 4
column: 0
@@ -17,7 +18,8 @@ expression: checks
end_location:
row: 4
column: 4
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 9
column: 4

View File

@@ -2,7 +2,8 @@
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 3
column: 0
@@ -17,7 +18,8 @@ expression: checks
end_location:
row: 3
column: 4
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 4
column: 0
@@ -32,7 +34,8 @@ expression: checks
end_location:
row: 4
column: 4
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 8
column: 4
@@ -47,7 +50,8 @@ expression: checks
end_location:
row: 8
column: 8
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 9
column: 4

View File

@@ -2,7 +2,8 @@
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 4
column: 0
@@ -17,7 +18,8 @@ expression: checks
end_location:
row: 4
column: 4
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 9
column: 4

View File

@@ -2,7 +2,8 @@
source: src/pylint/mod.rs
expression: checks
---
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: exit
location:
row: 1
column: 0
@@ -10,7 +11,8 @@ expression: checks
row: 1
column: 4
fix: ~
- kind: ConsiderUsingSysExit
- kind:
UseSysExit: quit
location:
row: 2
column: 0

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