Compare commits

...

14 Commits

Author SHA1 Message Date
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
78 changed files with 2408 additions and 815 deletions

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.164
rev: v0.0.167
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

6
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.167-dev.0"
dependencies = [
"anyhow",
"clap 4.0.29",
@@ -1821,7 +1821,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.164"
version = "0.0.167"
dependencies = [
"annotate-snippets 0.9.1",
"anyhow",
@@ -1874,7 +1874,7 @@ dependencies = [
[[package]]
name = "ruff_dev"
version = "0.0.164"
version = "0.0.167"
dependencies = [
"anyhow",
"clap 4.0.29",

View File

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

162
README.md
View File

@@ -68,31 +68,32 @@ 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 (I00)](#isort-i00)
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 (I25)](#flake8-tidy-imports-i25)
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 +146,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.167
hooks:
- id: ruff
```
@@ -195,6 +196,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 +395,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 +444,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 +467,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 +475,7 @@ For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
| ---- | ---- | ------- | --- |
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |
### isort
### isort (I00)
For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
@@ -476,7 +483,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 +534,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 +555,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 +577,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 +594,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 +612,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 +625,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 +643,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 +676,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 +688,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 +711,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 +719,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 +734,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 +745,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,7 +760,7 @@ 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 (I25)
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
@@ -754,7 +768,7 @@ For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports
| ---- | ---- | ------- | --- |
| I252 | BannedRelativeImport | Relative imports are banned | |
### eradicate
### eradicate (ERA)
For more, see [eradicate](https://pypi.org/project/eradicate/2.1.0/) on PyPI.
@@ -762,7 +776,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 +784,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.
@@ -786,7 +800,7 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| PLR1722 | ConsiderUsingSysExit | Consider using `sys.exit()` | 🛠 |
| PLW0120 | UselessElseOnLoop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
### Ruff-specific rules
### Ruff-specific rules (RUF)
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
@@ -940,6 +954,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/)
@@ -988,6 +1003,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/)
@@ -1758,6 +1774,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**: `BTreeMap<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**: `BTreeMap<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.167"
dependencies = [
"anyhow",
"clap",
@@ -1975,7 +1975,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.0.164"
version = "0.0.167"
dependencies = [
"anyhow",
"bincode",

View File

@@ -1,6 +1,6 @@
[package]
name = "flake8-to-ruff"
version = "0.0.164-dev.0"
version = "0.0.167-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

@@ -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.167"
edition = "2021"
[dependencies]

View File

@@ -76,6 +76,7 @@ pub fn main(cli: &Cli) -> Result<()> {
.new_enum("CheckCodePrefix")
.vis("pub")
.derive("EnumString")
.derive("AsRefStr")
.derive("Debug")
.derive("PartialEq")
.derive("Eq")
@@ -170,9 +171,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;");

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

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

@@ -35,8 +35,8 @@ 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, mccabe,
pep8_naming, pycodestyle, pydocstyle, pyflakes, pygrep_hooks, pylint, pyupgrade,
};
const GLOBAL_SCOPE_INDEX: usize = 0;
@@ -142,15 +142,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`.
@@ -727,6 +728,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 {
@@ -1063,9 +1077,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) {
@@ -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);
}
@@ -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);
@@ -2324,16 +2344,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) => {
@@ -3132,6 +3161,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 +3177,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

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

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
@@ -290,6 +292,8 @@ pub enum CheckCode {
FBT001,
FBT002,
FBT003,
// flake8-import-conventions
ICN001,
// Ruff
RUF001,
RUF002,
@@ -317,6 +321,7 @@ pub enum CheckCategory {
Flake8Builtins,
Flake8Comprehensions,
Flake8Debugger,
Flake8ImportConventions,
Flake8Print,
Flake8Quotes,
Flake8Return,
@@ -354,6 +359,7 @@ 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",
@@ -371,6 +377,41 @@ 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::I25],
CheckCategory::Isort => vec![CheckCodePrefix::I00],
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 +453,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,
@@ -589,7 +631,7 @@ pub enum CheckKind {
BuiltinArgumentShadowing(String),
BuiltinAttributeShadowing(String),
// flake8-blind-except
BlindExcept,
BlindExcept(String),
// flake8-bugbear
AbstractBaseClassWithoutAbstractMethod(String),
AssignmentToOsEnviron,
@@ -618,6 +660,7 @@ pub enum CheckKind {
UselessComparison,
UselessContextlibSuppress,
UselessExpression,
ZipWithoutExplicitStrict,
// flake8-comprehensions
UnnecessaryGeneratorList,
UnnecessaryGeneratorSet,
@@ -774,6 +817,8 @@ pub enum CheckKind {
BooleanPositionalValueInFunctionCall,
// pygrep-hooks
NoEval,
// flake8-import-conventions
ImportAliasIsNotConventional(String, String),
// Ruff
AmbiguousUnicodeCharacterString(char, char),
AmbiguousUnicodeCharacterDocstring(char, char),
@@ -924,6 +969,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,
@@ -999,7 +1045,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 +1159,10 @@ impl CheckCode {
CheckCode::FBT003 => CheckKind::BooleanPositionalValueInFunctionCall,
// pygrep-hooks
CheckCode::PGH001 => CheckKind::NoEval,
// flake8-import-conventions
CheckCode::ICN001 => {
CheckKind::ImportAliasIsNotConventional("...".to_string(), "...".to_string())
}
// Ruff
CheckCode::RUF001 => CheckKind::AmbiguousUnicodeCharacterString('𝐁', 'B'),
CheckCode::RUF002 => CheckKind::AmbiguousUnicodeCharacterDocstring('𝐁', 'B'),
@@ -1165,6 +1215,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,6 +1339,7 @@ impl CheckCode {
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::ICN001 => CheckCategory::Flake8ImportConventions,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::N801 => CheckCategory::PEP8Naming,
CheckCode::N802 => CheckCategory::PEP8Naming,
@@ -1465,6 +1517,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 +1529,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,
@@ -1633,6 +1686,8 @@ impl CheckKind {
CheckKind::BooleanPositionalValueInFunctionCall => &CheckCode::FBT003,
// pygrep-hooks
CheckKind::NoEval => &CheckCode::PGH001,
// flake8-import-conventions
CheckKind::ImportAliasIsNotConventional(..) => &CheckCode::ICN001,
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(..) => &CheckCode::RUF001,
CheckKind::AmbiguousUnicodeCharacterDocstring(..) => &CheckCode::RUF002,
@@ -1976,6 +2031,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 +2460,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 +2477,10 @@ impl CheckKind {
}
// pygrep-hooks
CheckKind::NoEval => "No builtin `eval()` allowed".to_string(),
// flake8-import-conventions
CheckKind::ImportAliasIsNotConventional(name, asname) => {
format!("`{name}` should be imported as `{asname}`")
}
// Ruff
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
format!(

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,
@@ -68,6 +70,7 @@ pub enum CheckCodePrefix {
B9,
B90,
B904,
B905,
BLE,
BLE0,
BLE00,
@@ -256,6 +259,10 @@ pub enum CheckCodePrefix {
I2,
I25,
I252,
ICN,
ICN0,
ICN00,
ICN001,
N,
N8,
N80,
@@ -513,6 +520,7 @@ impl CheckCodePrefix {
CheckCode::B026,
CheckCode::B027,
CheckCode::B904,
CheckCode::B905,
],
CheckCodePrefix::B0 => vec![
CheckCode::B002,
@@ -600,9 +608,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],
@@ -1138,6 +1147,10 @@ impl CheckCodePrefix {
CheckCodePrefix::I2 => vec![CheckCode::I252],
CheckCodePrefix::I25 => vec![CheckCode::I252],
CheckCodePrefix::I252 => vec![CheckCode::I252],
CheckCodePrefix::ICN => vec![CheckCode::ICN001],
CheckCodePrefix::ICN0 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN001 => vec![CheckCode::ICN001],
CheckCodePrefix::N => vec![
CheckCode::N801,
CheckCode::N802,
@@ -1719,6 +1732,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 +1921,10 @@ 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::N => SuffixLength::Zero,
CheckCodePrefix::N8 => SuffixLength::One,
CheckCodePrefix::N80 => SuffixLength::Two,
@@ -2087,6 +2105,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::F,
CheckCodePrefix::FBT,
CheckCodePrefix::I,
CheckCodePrefix::ICN,
CheckCodePrefix::N,
CheckCodePrefix::PGH,
CheckCodePrefix::PLC,

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,62 @@
use rustpython_ast::{Arguments, Expr, Stmt, StmtKind};
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, 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, 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,4 +1,4 @@
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;
@@ -6,6 +6,7 @@ use crate::ast::visitor::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`.

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

@@ -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,37 @@
use std::collections::BTreeMap;
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: &BTreeMap<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,100 @@
pub mod checks;
pub mod settings;
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use std::path::Path;
use anyhow::Result;
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(BTreeMap::from([
("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(BTreeMap::from([
("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(BTreeMap::from([(
"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,84 @@
//! Settings for import conventions.
use std::collections::BTreeMap;
use ruff_macros::ConfigurationOptions;
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 = "BTreeMap<String, String>",
example = r#"
# Declare the default aliases.
altair = "alt"
matplotlib.pyplot = "plt"
numpy = "np"
pandas = "pd"
seaborn = "sns"
"#
)]
pub aliases: Option<BTreeMap<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 = "BTreeMap<String, String>",
example = r#"
# Declare a custom alias for the `matplotlib` module.
"dask.dataframe" = "dd"
"#
)]
pub extend_aliases: Option<BTreeMap<String, String>>,
}
#[derive(Debug, Hash)]
pub struct Settings {
pub aliases: BTreeMap<String, String>,
}
fn default_aliases() -> BTreeMap<String, String> {
CONVENTIONAL_ALIASES
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect::<BTreeMap<_, _>>()
}
fn resolve_aliases(options: Options) -> BTreeMap<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

@@ -49,6 +49,7 @@ 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;

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

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

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

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

@@ -20,25 +20,28 @@ pub fn consider_merging_isinstance(
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()]
}
});
}
}
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 {

View File

@@ -7,14 +7,17 @@ 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),
));
}
}
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

@@ -61,27 +61,29 @@ 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);
}
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::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);
}
}

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

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

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

@@ -79,7 +79,9 @@ pub const BUILTINS: &[&str] = &[
"__package__",
"__spec__",
"abs",
"aiter",
"all",
"anext",
"any",
"ascii",
"bin",

View File

@@ -113,26 +113,28 @@ pub fn remove_super_arguments(locator: &SourceCodeLocator, expr: &Expr) -> Optio
let mut tree = libcst_native::parse_module(&contents, None).ok()?;
if let Some(Statement::Simple(body)) = tree.body.first_mut() {
if let Some(SmallStatement::Expr(body)) = body.body.first_mut() {
if let Expression::Call(body) = &mut body.value {
body.args = vec![];
body.whitespace_before_args = ParenthesizableWhitespace::default();
body.whitespace_after_func = ParenthesizableWhitespace::default();
let Statement::Simple(body) = tree.body.first_mut()? else {
return None;
};
let SmallStatement::Expr(body) = body.body.first_mut()? else {
return None;
};
let Expression::Call(body) = &mut body.value else {
return None;
};
let mut state = CodegenState::default();
tree.codegen(&mut state);
body.args = vec![];
body.whitespace_before_args = ParenthesizableWhitespace::default();
body.whitespace_after_func = ParenthesizableWhitespace::default();
return Some(Fix::replacement(
state.to_string(),
range.location,
range.end_location,
));
}
}
}
let mut state = CodegenState::default();
tree.codegen(&mut state);
None
Some(Fix::replacement(
state.to_string(),
range.location,
range.end_location,
))
}
/// UP010

View File

@@ -17,27 +17,27 @@ fn match_named_tuple_assign<'a>(
targets: &'a [Expr],
value: &'a Expr,
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a ExprKind)> {
if let Some(target) = targets.get(0) {
if let ExprKind::Name { id: typename, .. } = &target.node {
if let ExprKind::Call {
func,
args,
keywords,
} = &value.node
{
if match_module_member(
func,
"typing",
"NamedTuple",
&checker.from_imports,
&checker.import_aliases,
) {
return Some((typename, args, keywords, &func.node));
}
}
}
let target = targets.get(0)?;
let ExprKind::Name { id: typename, .. } = &target.node else {
return None;
};
let ExprKind::Call {
func,
args,
keywords,
} = &value.node else {
return None;
};
if !match_module_member(
func,
"typing",
"NamedTuple",
&checker.from_imports,
&checker.import_aliases,
) {
return None;
}
None
Some((typename, args, keywords, &func.node))
}
/// Generate a `StmtKind::AnnAssign` representing the provided property
@@ -78,13 +78,14 @@ fn create_property_assignment_stmt(
/// Match the `defaults` keyword in a `NamedTuple(...)` call.
fn match_defaults(keywords: &[Keyword]) -> Result<&[Expr]> {
match keywords.iter().find(|keyword| {
let defaults = keywords.iter().find(|keyword| {
if let Some(arg) = &keyword.node.arg {
arg.as_str() == "defaults"
} else {
false
}
}) {
});
match defaults {
Some(defaults) => match &defaults.node.value.node {
ExprKind::List { elts, .. } => Ok(elts),
ExprKind::Tuple { elts, .. } => Ok(elts),
@@ -96,53 +97,45 @@ fn match_defaults(keywords: &[Keyword]) -> Result<&[Expr]> {
/// Create a list of property assignments from the `NamedTuple` arguments.
fn create_properties_from_args(args: &[Expr], defaults: &[Expr]) -> Result<Vec<Stmt>> {
if let Some(fields) = args.get(1) {
if let ExprKind::List { elts, .. } = &fields.node {
let padded_defaults = if elts.len() >= defaults.len() {
std::iter::repeat(None)
.take(elts.len() - defaults.len())
.chain(defaults.iter().map(Some))
} else {
bail!("Defaults must be `None` or an iterable of at least the number of fields")
};
elts.iter()
.zip(padded_defaults)
.map(|(field, default)| {
if let ExprKind::Tuple { elts, .. } = &field.node {
if let [field_name, annotation] = elts.as_slice() {
if let ExprKind::Constant {
value: Constant::Str(property),
..
} = &field_name.node
{
if IDENTIFIER_REGEX.is_match(property)
&& !KWLIST.contains(&property.as_str())
{
Ok(create_property_assignment_stmt(
property,
&annotation.node,
default.map(|d| &d.node),
))
} else {
bail!("Invalid property name: {}", property)
}
} else {
bail!("Expected `field_name` to be `Constant::Str`")
}
} else {
bail!("Expected `elts` to have exactly two elements")
}
} else {
bail!("Expected `field` to be `ExprKind::Tuple`")
}
})
.collect()
} else {
bail!("Expected argument to be `ExprKind::List`")
}
let Some(fields) = args.get(1) else {
return Ok(vec![]);
};
let ExprKind::List { elts, .. } = &fields.node else {
bail!("Expected argument to be `ExprKind::List`");
};
let padded_defaults = if elts.len() >= defaults.len() {
std::iter::repeat(None)
.take(elts.len() - defaults.len())
.chain(defaults.iter().map(Some))
} else {
Ok(vec![])
}
bail!("Defaults must be `None` or an iterable of at least the number of fields")
};
elts.iter()
.zip(padded_defaults)
.map(|(field, default)| {
let ExprKind::Tuple { elts, .. } = &field.node else {
bail!("Expected `field` to be `ExprKind::Tuple`")
};
let [field_name, annotation] = elts.as_slice() else {
bail!("Expected `elts` to have exactly two elements")
};
let ExprKind::Constant {
value: Constant::Str(property),
..
} = &field_name.node else {
bail!("Expected `field_name` to be `Constant::Str`")
};
if !IDENTIFIER_REGEX.is_match(property) || KWLIST.contains(&property.as_str()) {
bail!("Invalid property name: {}", property)
}
Ok(create_property_assignment_stmt(
property,
&annotation.node,
default.map(|d| &d.node),
))
})
.collect()
}
/// Generate a `StmtKind:ClassDef` statement based on the provided body and
@@ -188,26 +181,27 @@ pub fn convert_named_tuple_functional_to_class(
targets: &[Expr],
value: &Expr,
) {
if let Some((typename, args, keywords, base_class)) =
match_named_tuple_assign(checker, targets, value)
let Some((typename, args, keywords, base_class)) =
match_named_tuple_assign(checker, targets, value) else
{
match match_defaults(keywords) {
Ok(defaults) => {
if let Ok(properties) = create_properties_from_args(args, defaults) {
let mut check = Check::new(
CheckKind::ConvertNamedTupleFunctionalToClass(typename.to_string()),
Range::from_located(stmt),
);
if checker.patch(check.kind.code()) {
match convert_to_class(stmt, typename, properties, base_class) {
Ok(fix) => check.amend(fix),
Err(err) => error!("Failed to convert `NamedTuple`: {err}"),
}
return;
};
match match_defaults(keywords) {
Ok(defaults) => {
if let Ok(properties) = create_properties_from_args(args, defaults) {
let mut check = Check::new(
CheckKind::ConvertNamedTupleFunctionalToClass(typename.to_string()),
Range::from_located(stmt),
);
if checker.patch(check.kind.code()) {
match convert_to_class(stmt, typename, properties, base_class) {
Ok(fix) => check.amend(fix),
Err(err) => error!("Failed to convert `NamedTuple`: {err}"),
}
checker.add_check(check);
}
checker.add_check(check);
}
Err(err) => error!("Failed to parse defaults: {err}"),
}
Err(err) => error!("Failed to parse defaults: {err}"),
}
}

View File

@@ -20,27 +20,27 @@ fn match_typed_dict_assign<'a>(
targets: &'a [Expr],
value: &'a Expr,
) -> Option<(&'a str, &'a [Expr], &'a [Keyword], &'a ExprKind)> {
if let Some(target) = targets.get(0) {
if let ExprKind::Name { id: class_name, .. } = &target.node {
if let ExprKind::Call {
func,
args,
keywords,
} = &value.node
{
if match_module_member(
func,
"typing",
"TypedDict",
&checker.from_imports,
&checker.import_aliases,
) {
return Some((class_name, args, keywords, &func.node));
}
}
}
let target = targets.get(0)?;
let ExprKind::Name { id: class_name, .. } = &target.node else {
return None;
};
let ExprKind::Call {
func,
args,
keywords,
} = &value.node else {
return None;
};
if !match_module_member(
func,
"typing",
"TypedDict",
&checker.from_imports,
&checker.import_aliases,
) {
return None;
}
None
Some((class_name, args, keywords, &func.node))
}
/// Generate a `StmtKind::AnnAssign` representing the provided property
@@ -127,15 +127,13 @@ fn get_properties_from_dict_literal(keys: &[Expr], values: &[Expr]) -> Result<Ve
}
fn get_properties_from_dict_call(func: &Expr, keywords: &[Keyword]) -> Result<Vec<Stmt>> {
if let ExprKind::Name { id, .. } = &func.node {
if id == "dict" {
get_properties_from_keywords(keywords)
} else {
bail!("Expected `id` to be `\"dict\"`")
}
} else {
let ExprKind::Name { id, .. } = &func.node else {
bail!("Expected `func` to be `ExprKind::Name`")
};
if id != "dict" {
bail!("Expected `id` to be `\"dict\"`")
}
get_properties_from_keywords(keywords)
}
// Deprecated in Python 3.11, removed in Python 3.13.
@@ -158,15 +156,11 @@ fn get_properties_from_keywords(keywords: &[Keyword]) -> Result<Vec<Stmt>> {
// The only way to have the `total` keyword is to use the args version, like:
// (`TypedDict('name', {'a': int}, total=True)`)
fn get_total_from_only_keyword(keywords: &[Keyword]) -> Option<&KeywordData> {
match keywords.get(0) {
Some(keyword) => match &keyword.node.arg {
Some(arg) => match arg.as_str() {
"total" => Some(&keyword.node),
_ => None,
},
None => None,
},
None => None,
let keyword = keywords.get(0)?;
let arg = &keyword.node.arg.as_ref()?;
match arg.as_str() {
"total" => Some(&keyword.node),
_ => None,
}
}
@@ -225,24 +219,27 @@ pub fn convert_typed_dict_functional_to_class(
targets: &[Expr],
value: &Expr,
) {
if let Some((class_name, args, keywords, base_class)) =
match_typed_dict_assign(checker, targets, value)
let Some((class_name, args, keywords, base_class)) =
match_typed_dict_assign(checker, targets, value) else
{
match get_properties_and_total(args, keywords) {
Err(err) => error!("Failed to parse TypedDict: {err}"),
Ok((body, total_keyword)) => {
let mut check = Check::new(
CheckKind::ConvertTypedDictFunctionalToClass(class_name.to_string()),
Range::from_located(stmt),
);
if checker.patch(check.kind.code()) {
match convert_to_class(stmt, class_name, body, total_keyword, base_class) {
Ok(fix) => check.amend(fix),
Err(err) => error!("Failed to convert TypedDict: {err}"),
};
}
checker.add_check(check);
}
return;
};
let (body, total_keyword) = match get_properties_and_total(args, keywords) {
Err(err) => {
error!("Failed to parse TypedDict: {err}");
return;
}
Ok(args) => args,
};
let mut check = Check::new(
CheckKind::ConvertTypedDictFunctionalToClass(class_name.to_string()),
Range::from_located(stmt),
);
if checker.patch(check.kind.code()) {
match convert_to_class(stmt, class_name, body, total_keyword, base_class) {
Ok(fix) => check.amend(fix),
Err(err) => error!("Failed to convert TypedDict: {err}"),
};
}
checker.add_check(check);
}

View File

@@ -29,24 +29,28 @@ static DEPRECATED_ALIASES: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::n
/// UP005
pub fn deprecated_unittest_alias(checker: &mut Checker, expr: &Expr) {
if let ExprKind::Attribute { value, attr, .. } = &expr.node {
if let Some(&target) = DEPRECATED_ALIASES.get(attr.as_str()) {
if let ExprKind::Name { id, .. } = &value.node {
if id == "self" {
let mut check = Check::new(
CheckKind::DeprecatedUnittestAlias(attr.to_string(), target.to_string()),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
format!("self.{target}"),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
}
}
let ExprKind::Attribute { value, attr, .. } = &expr.node else {
return;
};
let Some(&target) = DEPRECATED_ALIASES.get(attr.as_str()) else {
return;
};
let ExprKind::Name { id, .. } = &value.node else {
return;
};
if id != "self" {
return;
}
let mut check = Check::new(
CheckKind::DeprecatedUnittestAlias(attr.to_string(), target.to_string()),
Range::from_located(expr),
);
if checker.patch(check.kind.code()) {
check.amend(Fix::replacement(
format!("self.{target}"),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}

View File

@@ -9,20 +9,22 @@ use crate::pyupgrade::checks;
pub fn super_call_with_parameters(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
// Only bother going through the super check at all if we're in a `super` call.
// (We check this in `check_super_args` too, so this is just an optimization.)
if helpers::is_super_call_with_arguments(func, args) {
let scope = checker.current_scope();
let parents: Vec<&Stmt> = checker
.parent_stack
.iter()
.map(|index| checker.parents[*index])
.collect();
if let Some(mut check) = checks::super_args(scope, &parents, expr, func, args) {
if checker.patch(check.kind.code()) {
if let Some(fix) = pyupgrade::fixes::remove_super_arguments(checker.locator, expr) {
check.amend(fix);
}
}
checker.add_check(check);
if !helpers::is_super_call_with_arguments(func, args) {
return;
}
let scope = checker.current_scope();
let parents: Vec<&Stmt> = checker
.parent_stack
.iter()
.map(|index| checker.parents[*index])
.collect();
let Some(mut check) = checks::super_args(scope, &parents, expr, func, args) else {
return;
};
if checker.patch(check.kind.code()) {
if let Some(fix) = pyupgrade::fixes::remove_super_arguments(checker.locator, expr) {
check.amend(fix);
}
}
checker.add_check(check);
}

View File

@@ -8,16 +8,17 @@ use crate::pyupgrade::checks;
/// UP003
pub fn type_of_primitive(checker: &mut Checker, expr: &Expr, func: &Expr, args: &[Expr]) {
if let Some(mut check) = checks::type_of_primitive(func, args, Range::from_located(expr)) {
if checker.patch(check.kind.code()) {
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
check.amend(Fix::replacement(
primitive.builtin(),
expr.location,
expr.end_location.unwrap(),
));
}
let Some(mut check) = checks::type_of_primitive(func, args, Range::from_located(expr)) else {
return;
};
if checker.patch(check.kind.code()) {
if let CheckKind::TypeOfPrimitive(primitive) = &check.kind {
check.amend(Fix::replacement(
primitive.builtin(),
expr.location,
expr.end_location.unwrap(),
));
}
checker.add_check(check);
}
checker.add_check(check);
}

View File

@@ -9,17 +9,17 @@ use crate::source_code_locator::SourceCodeLocator;
const UTF8_LITERALS: &[&str] = &["utf-8", "utf8", "utf_8", "u8", "utf", "cp65001"];
fn match_encoded_variable(func: &Expr) -> Option<&Expr> {
if let ExprKind::Attribute {
let ExprKind::Attribute {
value: variable,
attr,
..
} = &func.node
{
if attr == "encode" {
return Some(variable);
}
} = &func.node else {
return None;
};
if attr != "encode" {
return None;
}
None
Some(variable)
}
fn is_utf8_encoding_arg(arg: &Expr) -> bool {
@@ -109,39 +109,27 @@ pub fn unnecessary_encode_utf8(
args: &Vec<Expr>,
kwargs: &Vec<Keyword>,
) {
if let Some(variable) = match_encoded_variable(func) {
match &variable.node {
ExprKind::Constant {
value: Constant::Str(literal),
..
} => {
// "str".encode()
// "str".encode("utf-8")
if is_default_encode(args, kwargs) {
if literal.is_ascii() {
// "foo".encode()
checker.add_check(replace_with_bytes_literal(
expr,
variable,
checker.locator,
checker.patch(&CheckCode::UP012),
));
} else {
// "unicode text©".encode("utf-8")
if let Some(check) = delete_default_encode_arg_or_kwarg(
expr,
args,
kwargs,
checker.patch(&CheckCode::UP012),
) {
checker.add_check(check);
}
}
}
}
// f"foo{bar}".encode(*args, **kwargs)
ExprKind::JoinedStr { .. } => {
if is_default_encode(args, kwargs) {
let Some(variable) = match_encoded_variable(func) else {
return;
};
match &variable.node {
ExprKind::Constant {
value: Constant::Str(literal),
..
} => {
// "str".encode()
// "str".encode("utf-8")
if is_default_encode(args, kwargs) {
if literal.is_ascii() {
// "foo".encode()
checker.add_check(replace_with_bytes_literal(
expr,
variable,
checker.locator,
checker.patch(&CheckCode::UP012),
));
} else {
// "unicode text©".encode("utf-8")
if let Some(check) = delete_default_encode_arg_or_kwarg(
expr,
args,
@@ -152,7 +140,20 @@ pub fn unnecessary_encode_utf8(
}
}
}
_ => {}
}
// f"foo{bar}".encode(*args, **kwargs)
ExprKind::JoinedStr { .. } => {
if is_default_encode(args, kwargs) {
if let Some(check) = delete_default_encode_arg_or_kwarg(
expr,
args,
kwargs,
checker.patch(&CheckCode::UP012),
) {
checker.add_check(check);
}
}
}
_ => {}
}
}

View File

@@ -49,36 +49,35 @@ pub fn unnecessary_future_import(checker: &mut Checker, stmt: &Stmt, names: &[Lo
}
}
if !removable_index.is_empty() {
let mut check = Check::new(
CheckKind::UnnecessaryFutureImport(
removable_names.into_iter().map(String::from).collect(),
),
Range::from_located(stmt),
);
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match fixes::remove_unnecessary_future_import(
checker.locator,
&removable_index,
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix);
}
Err(e) => error!("Failed to remove __future__ import: {e}"),
}
}
checker.add_check(check);
if removable_index.is_empty() {
return;
}
let mut check = Check::new(
CheckKind::UnnecessaryFutureImport(removable_names.into_iter().map(String::from).collect()),
Range::from_located(stmt),
);
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match fixes::remove_unnecessary_future_import(
checker.locator,
&removable_index,
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix);
}
Err(e) => error!("Failed to remove __future__ import: {e}"),
}
}
checker.add_check(check);
}

View File

@@ -6,15 +6,16 @@ use crate::pyupgrade::checks;
/// UP011
pub fn unnecessary_lru_cache_params(checker: &mut Checker, decorator_list: &[Expr]) {
if let Some(mut check) = checks::unnecessary_lru_cache_params(
let Some(mut check) = checks::unnecessary_lru_cache_params(
decorator_list,
checker.settings.target_version,
&checker.from_imports,
&checker.import_aliases,
) {
if checker.patch(check.kind.code()) {
check.amend(Fix::deletion(check.location, check.end_location));
}
checker.add_check(check);
) else {
return;
};
if checker.patch(check.kind.code()) {
check.amend(Fix::deletion(check.location, check.end_location));
}
checker.add_check(check);
}

View File

@@ -8,31 +8,31 @@ use crate::pyupgrade::checks;
/// UP001
pub fn useless_metaclass_type(checker: &mut Checker, stmt: &Stmt, value: &Expr, targets: &[Expr]) {
if let Some(mut check) =
checks::useless_metaclass_type(targets, value, Range::from_located(stmt))
{
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
let Some(mut check) =
checks::useless_metaclass_type(targets, value, Range::from_located(stmt)) else {
return;
};
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
let deleted: Vec<&Stmt> = checker
.deletions
.iter()
.map(|index| checker.parents[*index])
.collect();
match helpers::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
check.amend(fix);
match helpers::remove_stmt(
checker.parents[context.defined_by],
context.defined_in.map(|index| checker.parents[index]),
&deleted,
) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
}
Err(e) => error!("Failed to fix remove metaclass type: {e}"),
check.amend(fix);
}
Err(e) => error!("Failed to fix remove metaclass type: {e}"),
}
checker.add_check(check);
}
checker.add_check(check);
}

View File

@@ -13,18 +13,19 @@ pub fn useless_object_inheritance(
keywords: &[Keyword],
) {
let scope = checker.current_scope();
if let Some(mut check) = checks::useless_object_inheritance(name, bases, scope) {
if checker.patch(check.kind.code()) {
if let Some(fix) = pyupgrade::fixes::remove_class_def_base(
checker.locator,
stmt.location,
check.location,
bases,
keywords,
) {
check.amend(fix);
}
let Some(mut check) = checks::useless_object_inheritance(name, bases, scope) else {
return;
};
if checker.patch(check.kind.code()) {
if let Some(fix) = pyupgrade::fixes::remove_class_def_base(
checker.locator,
stmt.location,
check.location,
bases,
keywords,
) {
check.amend(fix);
}
checker.add_check(check);
}
checker.add_check(check);
}

View File

@@ -0,0 +1,59 @@
---
source: src/ruff/mod.rs
expression: checks
---
- kind:
AmbiguousUnicodeCharacterString:
- 𝐁
- B
location:
row: 1
column: 5
end_location:
row: 1
column: 6
fix:
content: B
location:
row: 1
column: 5
end_location:
row: 1
column: 6
- kind:
AmbiguousUnicodeCharacterDocstring:
-
- )
location:
row: 6
column: 55
end_location:
row: 6
column: 56
fix:
content: )
location:
row: 6
column: 55
end_location:
row: 6
column: 56
- kind:
AmbiguousUnicodeCharacterComment:
-
- /
location:
row: 7
column: 61
end_location:
row: 7
column: 62
fix:
content: /
location:
row: 7
column: 61
end_location:
row: 7
column: 62

View File

@@ -0,0 +1,6 @@
---
source: src/ruff/mod.rs
expression: checks
---
[]

View File

@@ -0,0 +1,6 @@
---
source: src/ruff/mod.rs
expression: checks
---
[]

View File

@@ -0,0 +1,185 @@
---
source: src/ruff/mod.rs
expression: checks
---
- kind:
UnusedNOQA: ~
location:
row: 9
column: 11
end_location:
row: 9
column: 17
fix:
content: ""
location:
row: 9
column: 9
end_location:
row: 9
column: 17
- kind:
UnusedNOQA:
- E501
location:
row: 13
column: 11
end_location:
row: 13
column: 23
fix:
content: ""
location:
row: 13
column: 9
end_location:
row: 13
column: 23
- kind:
UnusedNOQA:
- F841
- E501
location:
row: 16
column: 11
end_location:
row: 16
column: 29
fix:
content: ""
location:
row: 16
column: 9
end_location:
row: 16
column: 29
- kind:
UnusedNOQA:
- F841
- W191
location:
row: 19
column: 11
end_location:
row: 19
column: 29
fix:
content: ""
location:
row: 19
column: 9
end_location:
row: 19
column: 29
- kind:
UnusedNOQA:
- F841
- V101
location:
row: 22
column: 11
end_location:
row: 22
column: 29
fix:
content: ""
location:
row: 22
column: 9
end_location:
row: 22
column: 29
- kind:
UnusedNOQA:
- E501
location:
row: 26
column: 9
end_location:
row: 26
column: 21
fix:
content: ""
location:
row: 26
column: 9
end_location:
row: 26
column: 21
- kind:
UnusedVariable: d
location:
row: 29
column: 4
end_location:
row: 29
column: 5
fix: ~
- kind:
UnusedNOQA:
- E501
location:
row: 29
column: 32
end_location:
row: 29
column: 44
fix:
content: ""
location:
row: 29
column: 9
end_location:
row: 29
column: 44
- kind:
UnusedNOQA:
- F841
location:
row: 55
column: 5
end_location:
row: 55
column: 23
fix:
content: "# noqa: E501"
location:
row: 55
column: 5
end_location:
row: 55
column: 23
- kind:
UnusedNOQA:
- E501
location:
row: 63
column: 5
end_location:
row: 63
column: 17
fix:
content: ""
location:
row: 63
column: 3
end_location:
row: 63
column: 17
- kind:
UnusedNOQA: ~
location:
row: 71
column: 5
end_location:
row: 71
column: 11
fix:
content: ""
location:
row: 71
column: 3
end_location:
row: 71
column: 11

View File

@@ -0,0 +1,6 @@
---
source: src/ruff/mod.rs
expression: checks
---
[]

View File

@@ -14,8 +14,8 @@ use crate::checks_gen::{CheckCodePrefix, CATEGORIES};
use crate::settings::pyproject::load_options;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
pep8_naming, pyupgrade,
flake8_annotations, flake8_bugbear, flake8_import_conventions, flake8_quotes,
flake8_tidy_imports, fs, isort, mccabe, pep8_naming, pyupgrade,
};
#[derive(Debug)]
@@ -42,6 +42,7 @@ pub struct Configuration {
// Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_bugbear: flake8_bugbear::settings::Settings,
pub flake8_import_conventions: flake8_import_conventions::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings,
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
pub isort: isort::settings::Settings,
@@ -152,6 +153,10 @@ impl Configuration {
.flake8_bugbear
.map(flake8_bugbear::settings::Settings::from_options)
.unwrap_or_default(),
flake8_import_conventions: options
.flake8_import_conventions
.map(flake8_import_conventions::settings::Settings::from_options)
.unwrap_or_default(),
flake8_quotes: options
.flake8_quotes
.map(flake8_quotes::settings::Settings::from_options)
@@ -166,6 +171,7 @@ impl Configuration {
.unwrap_or_default(),
mccabe: options
.mccabe
.as_ref()
.map(mccabe::settings::Settings::from_options)
.unwrap_or_default(),
pep8_naming: options

View File

@@ -17,8 +17,8 @@ use crate::checks_gen::{CheckCodePrefix, SuffixLength};
use crate::settings::configuration::Configuration;
use crate::settings::types::{FilePattern, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, fs, isort, mccabe,
pep8_naming, pyupgrade,
flake8_annotations, flake8_bugbear, flake8_import_conventions, flake8_quotes,
flake8_tidy_imports, fs, isort, mccabe, pep8_naming, pyupgrade,
};
pub mod configuration;
@@ -46,6 +46,7 @@ pub struct Settings {
// Plugins
pub flake8_annotations: flake8_annotations::settings::Settings,
pub flake8_bugbear: flake8_bugbear::settings::Settings,
pub flake8_import_conventions: flake8_import_conventions::settings::Settings,
pub flake8_quotes: flake8_quotes::settings::Settings,
pub flake8_tidy_imports: flake8_tidy_imports::settings::Settings,
pub isort: isort::settings::Settings,
@@ -81,6 +82,7 @@ impl Settings {
format: config.format,
flake8_annotations: config.flake8_annotations,
flake8_bugbear: config.flake8_bugbear,
flake8_import_conventions: config.flake8_import_conventions,
flake8_quotes: config.flake8_quotes,
flake8_tidy_imports: config.flake8_tidy_imports,
ignore_init_module_imports: config.ignore_init_module_imports,
@@ -114,6 +116,7 @@ impl Settings {
target_version: PythonVersion::Py310,
flake8_annotations: flake8_annotations::settings::Settings::default(),
flake8_bugbear: flake8_bugbear::settings::Settings::default(),
flake8_import_conventions: flake8_import_conventions::settings::Settings::default(),
flake8_quotes: flake8_quotes::settings::Settings::default(),
flake8_tidy_imports: flake8_tidy_imports::settings::Settings::default(),
isort: isort::settings::Settings::default(),
@@ -141,6 +144,7 @@ impl Settings {
target_version: PythonVersion::Py310,
flake8_annotations: flake8_annotations::settings::Settings::default(),
flake8_bugbear: flake8_bugbear::settings::Settings::default(),
flake8_import_conventions: flake8_import_conventions::settings::Settings::default(),
flake8_quotes: flake8_quotes::settings::Settings::default(),
flake8_tidy_imports: flake8_tidy_imports::settings::Settings::default(),
isort: isort::settings::Settings::default(),
@@ -176,6 +180,7 @@ impl Hash for Settings {
// Add plugin properties in alphabetical order.
self.flake8_annotations.hash(state);
self.flake8_bugbear.hash(state);
self.flake8_import_conventions.hash(state);
self.flake8_quotes.hash(state);
self.flake8_tidy_imports.hash(state);
self.isort.hash(state);

View File

@@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize};
use crate::checks_gen::CheckCodePrefix;
use crate::settings::types::{PythonVersion, SerializationFormat};
use crate::{
flake8_annotations, flake8_bugbear, flake8_quotes, flake8_tidy_imports, isort, mccabe,
pep8_naming, pyupgrade,
flake8_annotations, flake8_bugbear, flake8_import_conventions, flake8_quotes,
flake8_tidy_imports, isort, mccabe, pep8_naming, pyupgrade,
};
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Default, ConfigurationOptions)]
@@ -261,6 +261,8 @@ pub struct Options {
#[option_group]
pub flake8_tidy_imports: Option<flake8_tidy_imports::settings::Options>,
#[option_group]
pub flake8_import_conventions: Option<flake8_import_conventions::settings::Options>,
#[option_group]
pub isort: Option<isort::settings::Options>,
#[option_group]
pub mccabe: Option<mccabe::settings::Options>,

View File

@@ -96,6 +96,7 @@ pub fn load_options(pyproject: Option<&PathBuf>) -> Result<Options> {
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use std::env::current_dir;
use std::path::PathBuf;
use std::str::FromStr;
@@ -110,7 +111,10 @@ mod tests {
find_project_root, find_pyproject_toml, parse_pyproject_toml, Options, Pyproject, Tools,
};
use crate::settings::types::PatternPrefixPair;
use crate::{flake8_bugbear, flake8_quotes, flake8_tidy_imports, mccabe, pep8_naming};
use crate::{
flake8_bugbear, flake8_import_conventions, flake8_quotes, flake8_tidy_imports, mccabe,
pep8_naming,
};
#[test]
fn deserialize() -> Result<()> {
@@ -157,6 +161,7 @@ mod tests {
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -199,6 +204,7 @@ line-length = 79
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -241,6 +247,7 @@ exclude = ["foo.py"]
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -283,6 +290,7 @@ select = ["E501"]
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -326,6 +334,7 @@ ignore = ["E501"]
flake8_bugbear: None,
flake8_quotes: None,
flake8_tidy_imports: None,
flake8_import_conventions: None,
isort: None,
mccabe: None,
pep8_naming: None,
@@ -422,6 +431,13 @@ other-attribute = 1
flake8_tidy_imports: Some(flake8_tidy_imports::settings::Options {
ban_relative_imports: Some(Strictness::Parents)
}),
flake8_import_conventions: Some(flake8_import_conventions::settings::Options {
aliases: Some(BTreeMap::from([("pandas".to_string(), "pd".to_string(),)])),
extend_aliases: Some(BTreeMap::from([(
"dask.dataframe".to_string(),
"dd".to_string(),
)])),
}),
isort: None,
mccabe: Some(mccabe::settings::Options {
max_complexity: Some(10),