Compare commits

...

41 Commits

Author SHA1 Message Date
Charlie Marsh
63b3e00c97 Bump version to 0.0.174 2022-12-10 12:08:48 -05:00
Charlie Marsh
39440aa274 Create function and lambda scopes eagerly (#1181) 2022-12-10 12:08:33 -05:00
Charlie Marsh
add96d3dc5 Implement E0117 (nonlocal-without-binding) (#1180) 2022-12-10 11:41:57 -05:00
Charlie Marsh
6f8e0224d0 Implement W0602 (global-variable-not-assigned) (#1179) 2022-12-10 11:33:24 -05:00
Charlie Marsh
b8bbafd85b Flag global usages prior to global declarations (#1178) 2022-12-10 11:19:24 -05:00
Charlie Marsh
40b54d3e8c Ignore imports in class scopes (#1176) 2022-12-10 10:23:33 -05:00
Charlie Marsh
2b44941d63 Add pacman instructions to README (#1175) 2022-12-10 10:00:01 -05:00
Charlie Marsh
257bd7f1d7 Bump version to 0.0.173 2022-12-09 23:23:12 -05:00
Charlie Marsh
5728dceef0 Add note around redefinitions 2022-12-09 23:18:51 -05:00
Charlie Marsh
6739602806 Mark redefined-but-unused imports as unused regardless of scope (#1173) 2022-12-09 23:17:33 -05:00
Charlie Marsh
305326f7d7 Remove some string clones from docstring helpers (#1172) 2022-12-09 22:30:34 -05:00
Charlie Marsh
69866f5461 Extract docstring exactly once (#1171) 2022-12-09 22:21:16 -05:00
Charlie Marsh
41ca29c4f4 Add TODO in redefined_by_function 2022-12-09 21:19:57 -05:00
Charlie Marsh
b35a804f9d Bump version to 0.0.172 2022-12-09 17:47:34 -05:00
Charlie Marsh
e594ed6528 Implement D301 (backslash checks) (#1169) 2022-12-09 17:44:18 -05:00
Charlie Marsh
197645d90d Always use raw docstrings for pydocstyle rules (#1167) 2022-12-09 17:31:04 -05:00
Charlie Marsh
26d3ff5a3a Add pyflakes test suite for annotations (#1166) 2022-12-09 16:28:07 -05:00
Charlie Marsh
0dacf61153 Implement F842 (UnusedAnnotation) (#1165) 2022-12-09 12:42:03 -05:00
Charlie Marsh
a6251360b7 Avoid RET false-positives for usages in f-strings (#1163) 2022-12-09 12:28:09 -05:00
Charlie Marsh
2965e2561d Clarify combination of combine-as-imports and force-wrap-aliases (#1162) 2022-12-09 12:20:15 -05:00
Charlie Marsh
a19050b8a4 Update README.md 2022-12-08 23:39:01 -05:00
Charlie Marsh
dfd6225d85 Bump version to 0.0.171 2022-12-08 23:18:48 -05:00
Charlie Marsh
a0a6327fae Only allowlist noqa et al at the start of a comment (#1157) 2022-12-08 23:10:36 -05:00
Charlie Marsh
db815a565f Run release job on release: published event (#1156) 2022-12-08 23:05:28 -05:00
Charlie Marsh
3bacdafd1c Improve some __all__ handling cases (#1155) 2022-12-08 23:03:23 -05:00
Charlie Marsh
6403e3630d Fix flaky unused import test 2022-12-08 22:51:13 -05:00
Charlie Marsh
229eab6f42 Improve some behavior around global handling (#1154) 2022-12-08 22:47:19 -05:00
Charlie Marsh
e33582fb0e Add pyflakes import test suite (#1151) 2022-12-08 22:23:37 -05:00
Charlie Marsh
aaeab0ecf1 Implement F811 (RedefinedWhileUnused) (#1137) 2022-12-08 21:31:08 -05:00
Charlie Marsh
f9a16d9c44 Fix GitHub link 2022-12-08 20:54:54 -05:00
Charlie Marsh
2aa884eb9b Re-implement the entire test_undefined_names.py test suite (#1150) 2022-12-08 20:53:01 -05:00
Charlie Marsh
84fa64d98c Move bindings to an arena (#1147) 2022-12-08 19:48:00 -05:00
Charlie Marsh
c1b1ac069e Include else block in break detection (#1143) 2022-12-08 11:53:31 -05:00
Charlie Marsh
a710e35ebc Bump version to 0.0.170 2022-12-08 11:36:24 -05:00
Charlie Marsh
49df43bb78 Use single newlines in .pyi import sorting (#1142) 2022-12-08 11:34:41 -05:00
Charlie Marsh
e338d9acbe Remove 'consider' language from check messages (#1135) 2022-12-07 20:10:36 -05:00
Charlie Marsh
5c8655f479 Bump ruff_macros to 0.0.169 2022-12-07 19:10:16 -05:00
Charlie Marsh
60987888a2 Re-increase max iterations to 100 2022-12-07 19:10:03 -05:00
Charlie Marsh
a81581c781 Bump ruff_macros to 0.0.168 2022-12-07 19:08:18 -05:00
Charlie Marsh
3152dd7a8e Don't prompt users to --fix if they ran with --fix (#1133) 2022-12-07 19:07:51 -05:00
Charlie Marsh
528416f07a Rename I252 to TID252; add redirects for all renamed codes (#1129) 2022-12-07 15:12:22 -05:00
124 changed files with 6670 additions and 1183 deletions

View File

@@ -1,9 +1,8 @@
name: "[ruff] Release"
on:
create:
tags:
- v*
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

View File

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

8
Cargo.lock generated
View File

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

View File

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

View File

@@ -72,7 +72,7 @@ of [Conda](https://docs.conda.io/en/latest/):
1. [Pyflakes (F)](#pyflakes-f)
1. [pycodestyle (E, W)](#pycodestyle-e-w)
1. [mccabe (C90)](#mccabe-c90)
1. [isort (I00)](#isort-i00)
1. [isort (I)](#isort-i)
1. [pydocstyle (D)](#pydocstyle-d)
1. [pyupgrade (UP)](#pyupgrade-up)
1. [pep8-naming (N)](#pep8-naming-n)
@@ -89,7 +89,7 @@ of [Conda](https://docs.conda.io/en/latest/):
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. [flake8-tidy-imports (TID)](#flake8-tidy-imports-tid)
1. [flake8-unused-arguments (ARG)](#flake8-unused-arguments-arg)
1. [eradicate (ERA)](#eradicate-era)
1. [pygrep-hooks (PGH)](#pygrep-hooks-pgh)
@@ -120,12 +120,18 @@ For **macOS Homebrew** and **Linuxbrew** users, Ruff is also available as [`ruff
brew install ruff
```
For Conda users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:
For **Conda** users, Ruff is also available as [`ruff`](https://anaconda.org/conda-forge/ruff) on `conda-forge`:
```shell
conda install -c conda-forge ruff
```
For **Arch Linux** users, Ruff is also available as [`ruff`](https://archlinux.org/packages/community/x86_64/ruff/) on the official repositories:
```shell
pacman -S ruff
```
### Usage
To run Ruff, try any of the following:
@@ -147,7 +153,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
```yaml
repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.168
rev: v0.0.174
hooks:
- id: ruff
```
@@ -438,11 +444,13 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | |
| F707 | DefaultExceptNotLast | An `except` block as not the last exception handler | |
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | |
| F811 | RedefinedWhileUnused | Redefinition of unused `...` from line 1 | |
| F821 | UndefinedName | Undefined name `...` | |
| F822 | UndefinedExport | Undefined name `...` in `__all__` | |
| F823 | UndefinedLocal | Local variable `...` referenced before assignment | |
| F831 | DuplicateArgumentName | Duplicate argument name in function definition | |
| F841 | UnusedVariable | Local variable `...` is assigned to but never used | |
| F842 | UnusedAnnotation | Local variable `...` is annotated but never used | |
| F901 | RaiseNotImplemented | `raise NotImplemented` should be `raise NotImplementedError` | 🛠 |
### pycodestyle (E, W)
@@ -476,7 +484,7 @@ For more, see [mccabe](https://pypi.org/project/mccabe/0.7.0/) on PyPI.
| ---- | ---- | ------- | --- |
| C901 | FunctionIsTooComplex | `...` is too complex (10) | |
### isort (I00)
### isort (I)
For more, see [isort](https://pypi.org/project/isort/5.10.1/) on PyPI.
@@ -515,6 +523,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
| D214 | SectionNotOverIndented | Section is over-indented ("Returns") | 🛠 |
| D215 | SectionUnderlineNotOverIndented | Section underline is over-indented ("Returns") | 🛠 |
| D300 | UsesTripleQuotes | Use """triple double quotes""" | |
| D301 | UsesRPrefixForBackslashedContent | Use r""" if any backslashes in a docstring | |
| D400 | EndsInPeriod | First line should end with a period | 🛠 |
| D402 | NoSignature | First line should not be the function's signature | |
| D403 | FirstLineCapitalized | First word of the first line should be properly capitalized | |
@@ -761,13 +770,13 @@ 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 (I25)
### flake8-tidy-imports (TID)
For more, see [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/4.8.0/) on PyPI.
| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| I252 | BannedRelativeImport | Relative imports are banned | |
| TID252 | BannedRelativeImport | Relative imports are banned | |
### flake8-unused-arguments (ARG)
@@ -806,11 +815,12 @@ For more, see [Pylint](https://pypi.org/project/pylint/2.15.7/) on PyPI.
| PLC0414 | UselessImportAlias | Import alias does not rename original package | 🛠 |
| PLC2201 | MisplacedComparisonConstant | Comparison should be ... | 🛠 |
| PLC3002 | UnnecessaryDirectLambdaCall | Lambda expression called directly. Execute the expression inline instead. | |
| PLE0118 | UsedPriorGlobalDeclaration | Name `...` is used prior to global declaration on line 1 | |
| PLE1142 | AwaitOutsideAsync | `await` should be used within an async function | |
| PLR0206 | PropertyWithParameters | Cannot have defined parameters for properties | |
| PLR0402 | ConsiderUsingFromImport | Consider using `from ... import ...` | |
| PLR1701 | ConsiderMergingIsinstance | Consider merging these isinstance calls: `isinstance(..., (...))` | |
| PLR1722 | ConsiderUsingSysExit | Consider using `sys.exit()` | 🛠 |
| PLR0402 | ConsiderUsingFromImport | Use `from ... import ...` in lieu of alias | |
| PLR1701 | ConsiderMergingIsinstance | Merge these isinstance calls: `isinstance(..., (...))` | |
| PLR1722 | UseSysExit | Use `sys.exit()` instead of `exit` | 🛠 |
| PLW0120 | UselessElseOnLoop | Else clause on loop without a break statement, remove the else and de-indent all the code inside it | |
### Ruff-specific rules (RUF)
@@ -948,7 +958,7 @@ automatically convert your existing configuration.)
Ruff can be used as a drop-in replacement for Flake8 when used (1) without or with a small number of
plugins, (2) alongside Black, and (3) on Python 3 code.
Under those conditions, Ruff implements every rule in Flake8, with the exception of `F811`.
Under those conditions, Ruff implements every rule in Flake8.
Ruff also re-implements some of the most popular Flake8 plugins and related code quality tools
natively, including:
@@ -980,6 +990,12 @@ natively, including:
- [`pygrep-hooks`](https://github.com/pre-commit/pygrep-hooks) (1/10)
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
Note that, in some cases, Ruff uses different error code prefixes than would be found in the
originating Flake8 plugins. For example, Ruff uses `TID252` to represent the `I252` rule from
`flake8-tidy-imports`. This helps minimize conflicts across plugins and allows any individual plugin
to be toggled on or off with a single (e.g.) `--select TID`, as opposed to `--select I2` (to avoid
conflicts with the `isort` rules, like `I001`).
Beyond the rule set, Ruff suffers from the following limitations vis-à-vis Flake8:
1. Ruff does not yet support a few Python 3.9 and 3.10 language features, including structural
@@ -1478,7 +1494,7 @@ fix = true
A list of check code prefixes to consider autofix-able.
**Default value**: `["A", "ANN", "B", "BLE", "C", "D", "E", "F", "FBT", "I", "M", "N", "Q", "RUF", "S", "T", "U", "W", "YTT"]`
**Default value**: `["A", "ANN", "ARG", "B", "BLE", "C", "D", "E", "ERA", "F", "FBT", "I", "ICN", "N", "PGH", "PLC", "PLE", "PLR", "PLW", "Q", "RET", "RUF", "S", "T", "TID", "UP", "W", "YTT"]`
**Type**: `Vec<CheckCodePrefix>`
@@ -1795,7 +1811,7 @@ The conventional aliases for imports. These aliases can be extended by the `exte
**Default value**: `{"altair": "alt", "matplotlib.pyplot": "plt", "numpy": "np", "pandas": "pd", "seaborn": "sns"}`
**Type**: `BTreeMap<String, String>`
**Type**: `FxHashMap<String, String>`
**Example usage**:
@@ -1817,7 +1833,7 @@ A mapping of modules to their conventional import aliases. These aliases will be
**Default value**: `{}`
**Type**: `BTreeMap<String, String>`
**Type**: `FxHashMap<String, String>`
**Example usage**:
@@ -1974,6 +1990,10 @@ from .utils import (
)
```
Note that this setting is only effective when combined with `combine-as-imports = true`.
When `combine-as-imports` isn't enabled, every aliased `import from` will be given its
own line, in which case, wrapping is not necessary.
**Default value**: `false`
**Type**: `bool`
@@ -1983,6 +2003,7 @@ from .utils import (
```toml
[tool.ruff.isort]
force-wrap-aliases = true
combine-as-imports = true
```
---

View File

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

View File

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

View File

@@ -512,6 +512,7 @@ mod tests {
CheckCodePrefix::D214,
CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
CheckCodePrefix::D403,
CheckCodePrefix::D404,

View File

@@ -135,7 +135,6 @@ fn tokenize_files_to_codes_mapping(value: &str) -> Vec<Token> {
}
/// Parse a 'files-to-codes' mapping, mimicking Flake8's internal logic.
///
/// See: <https://github.com/PyCQA/flake8/blob/7dfe99616fc2f07c0017df2ba5fa884158f3ea8a/src/flake8/utils.py#L45>
pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair>> {
if value.trim().is_empty() {

View File

@@ -162,6 +162,7 @@ impl DocstringConvention {
// CheckCodePrefix::D214,
// CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
CheckCodePrefix::D402,
CheckCodePrefix::D403,
@@ -209,6 +210,7 @@ impl DocstringConvention {
CheckCodePrefix::D214,
CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
CheckCodePrefix::D400,
// CheckCodePrefix::D402,
CheckCodePrefix::D403,
@@ -257,6 +259,7 @@ impl DocstringConvention {
CheckCodePrefix::D214,
// CheckCodePrefix::D215,
CheckCodePrefix::D300,
CheckCodePrefix::D301,
// CheckCodePrefix::D400,
CheckCodePrefix::D402,
CheckCodePrefix::D403,

View File

@@ -5,9 +5,11 @@ a = 4
#foo(1, 2, 3)
def foo(x, y, z):
contentet = 1 # print('hello')
content = 1 # print('hello')
print(x, y, z)
# This is a real comment.
#return True
return False
#import os # noqa: ERA001

View File

@@ -130,6 +130,12 @@ def x():
return val
def x():
a = 1
print(f"a={a}")
return a
# Test cases for using value for assignment then returning it
# See:https://github.com/afonasev/flake8-return/issues/47
def resolve_from_url(self, url: str) -> dict:

View File

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

View File

@@ -0,0 +1,11 @@
def foo(x):
return x
@foo
def bar():
pass
def bar():
pass

View File

@@ -0,0 +1 @@
import fu as FU, bar as FU

View File

@@ -0,0 +1,8 @@
"""Test that importing a module twice using a nested does not issue a warning."""
try:
if True:
if True:
import os
except:
import os
os.path

View File

@@ -0,0 +1,9 @@
try:
from aa import mixer
except AttributeError:
from bb import mixer
except RuntimeError:
from cc import mixer
except:
from dd import mixer
mixer(123)

View File

@@ -0,0 +1,7 @@
try:
from aa import mixer
except ImportError:
pass
else:
from bb import mixer
mixer(123)

View File

@@ -0,0 +1,8 @@
try:
import funca
except ImportError:
from bb import funca
from bb import funcb
else:
from bbb import funcb
print(funca, funcb)

View File

@@ -0,0 +1,10 @@
try:
import b
except ImportError:
b = Ellipsis
from bb import a
else:
from aa import a
finally:
a = 42
print(a, b)

View File

@@ -0,0 +1,5 @@
import fu
def fu():
pass

View File

@@ -0,0 +1,9 @@
"""Test that shadowing a global with a nested function generates a warning."""
import fu
def bar():
def baz():
def fu():
pass

View File

@@ -0,0 +1,10 @@
"""Test that shadowing a global name with a nested function generates a warning."""
import fu
def bar():
import fu
def baz():
def fu():
pass

View File

@@ -0,0 +1,14 @@
"""Test that a global import which is redefined locally, but used later in another scope does not generate a warning."""
import unittest, transport
class GetTransportTestCase(unittest.TestCase):
def test_get_transport(self):
transport = 'transport'
self.assertIsNotNone(transport)
class TestTransportMethodArgs(unittest.TestCase):
def test_send_defaults(self):
transport.Transport()

View File

@@ -0,0 +1,7 @@
"""If an imported name is redefined by a class statement which also uses that name in the bases list, no warning is emitted."""
from fu import bar
class bar(bar):
pass

View File

@@ -0,0 +1 @@
from moo import fu as FU, bar as FU

View File

@@ -0,0 +1,14 @@
"""
Test that shadowing a global with a class attribute does not produce a
warning.
"""
import fu
class bar:
# STOPSHIP: This errors.
fu = 1
print(fu)

View File

@@ -0,0 +1 @@
import fu; fu = 3

View File

@@ -0,0 +1 @@
import fu; fu, bar = 3

View File

@@ -0,0 +1 @@
import fu; [fu, bar] = 3

View File

@@ -0,0 +1,7 @@
"""Test that importing a module twice within an if block does raise a warning."""
i = 2
if i == 1:
import os
import os
os.path

View File

@@ -0,0 +1,8 @@
"""Test that importing a module twice in if-else blocks does not raise a warning."""
i = 2
if i == 1:
import os
else:
import os
os.path

View File

@@ -0,0 +1,8 @@
"""Test that importing a module twice in a try block does raise a warning."""
try:
import os
import os
except:
pass
os.path

View File

@@ -0,0 +1,7 @@
"""Test that importing a module twice in a try block does not raise a warning."""
try:
import os
except:
import os
os.path

View File

@@ -0,0 +1,13 @@
def f():
name: str
age: int
class A:
name: str
age: int
class B:
name: str = "Bob"
age: int = 18

View File

@@ -0,0 +1,32 @@
###
# Errors.
###
def f():
global x
def f():
global x
print(x)
###
# Non-errors.
###
def f():
global x
x = 1
def f():
global x
(x, y) = (1, 2)
def f():
global x
del x

View File

@@ -0,0 +1,19 @@
nonlocal x
def f():
nonlocal x
def f():
nonlocal y
def f():
x = 1
def f():
nonlocal x
def f():
nonlocal y

View File

@@ -0,0 +1,148 @@
###
# Errors.
###
def f():
print(x)
global x
print(x)
def f():
global x
print(x)
global x
print(x)
def f():
print(x)
global x, y
print(x)
def f():
global x, y
print(x)
global x, y
print(x)
def f():
x = 1
global x
x = 1
def f():
global x
x = 1
global x
x = 1
def f():
del x
global x, y
del x
def f():
global x, y
del x
global x, y
del x
def f():
del x
global x
del x
def f():
global x
del x
global x
del x
def f():
del x
global x, y
del x
def f():
global x, y
del x
global x, y
del x
###
# Non-errors.
###
def f():
global x
print(x)
def f():
global x, y
print(x)
def f():
global x
x = 1
def f():
global x, y
x = 1
def f():
global x
del x
def f():
global x, y
del x

View File

@@ -75,7 +75,7 @@ def test_break_in_orelse_deep():
def test_break_in_orelse_deep2():
"""should rise a useless-else-on-loop message, as the break statement is only
"""should raise a useless-else-on-loop message, as the break statement is only
for the inner for loop
"""
for _ in range(10):
@@ -101,3 +101,15 @@ def test_break_in_orelse_deep3():
else:
return True
return False
def test_break_in_if_orelse():
"""should raise a useless-else-on-loop message due to break in else"""
for _ in range(10):
if 1 < 2: # pylint: disable=comparison-of-constants
pass
else:
break
else:
return True
return False

View File

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

View File

@@ -8,7 +8,7 @@ use anyhow::Result;
use clap::Parser;
use codegen::{Scope, Type, Variant};
use itertools::Itertools;
use ruff::checks::{CheckCode, REDIRECTS};
use ruff::checks::{CheckCode, CODE_REDIRECTS, PREFIX_REDIRECTS};
use strum::IntoEnumIterator;
const FILE: &str = "src/checks_gen.rs";
@@ -39,34 +39,26 @@ pub fn main(cli: &Cli) -> Result<()> {
}
}
// Add any aliases (e.g., "U001" to "UP001").
for (alias, check_code) in REDIRECTS.iter() {
// Compute the length of the prefix and suffix for both codes.
let code_str: String = check_code.as_ref().to_string();
let code_prefix_len = code_str
.chars()
.take_while(|char| char.is_alphabetic())
.count();
let code_suffix_len = code_str.len() - code_prefix_len;
let alias_prefix_len = alias
.chars()
.take_while(|char| char.is_alphabetic())
.count();
let alias_suffix_len = alias.len() - alias_prefix_len;
assert_eq!(code_suffix_len, alias_suffix_len);
for i in 0..=code_suffix_len {
let source = code_str[..code_prefix_len + i].to_string();
let destination = alias[..alias_prefix_len + i].to_string();
if source != destination {
prefix_to_codes.insert(
destination,
prefix_to_codes
.get(&source)
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
.clone(),
);
}
}
// Add any prefix aliases (e.g., "U" to "UP").
for (alias, source) in PREFIX_REDIRECTS.iter() {
prefix_to_codes.insert(
(*alias).to_string(),
prefix_to_codes
.get(&(*source).to_string())
.unwrap_or_else(|| panic!("Unknown CheckCode: {source:?}"))
.clone(),
);
}
// Add any check code aliases (e.g., "U001" to "UP001").
for (alias, check_code) in CODE_REDIRECTS.iter() {
prefix_to_codes.insert(
(*alias).to_string(),
prefix_to_codes
.get(&check_code.as_ref().to_string())
.unwrap_or_else(|| panic!("Unknown CheckCode: {alias:?}"))
.clone(),
);
}
let mut scope = Scope::new();
@@ -113,10 +105,10 @@ pub fn main(cli: &Cli) -> Result<()> {
.line("#[allow(clippy::match_same_arms)]")
.line("match self {");
for (prefix, codes) in &prefix_to_codes {
if let Some(target) = REDIRECTS.get(&prefix.as_str()) {
if let Some(target) = CODE_REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been renamed to \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
target.as_ref(),
@@ -125,6 +117,18 @@ pub fn main(cli: &Cli) -> Result<()> {
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
} else if let Some(target) = PREFIX_REDIRECTS.get(&prefix.as_str()) {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => {{ eprintln!(\"{{}}{{}} {{}}\", \
\"warning\".yellow().bold(), \":\".bold(), \"`{}` has been remapped to \
`{}`\".bold()); \n vec![{}] }}",
prefix,
target,
codes
.iter()
.map(|code| format!("CheckCode::{}", code.as_ref()))
.join(", ")
));
} else {
gen = gen.line(format!(
"CheckCodePrefix::{prefix} => vec![{}],",
@@ -187,7 +191,9 @@ pub fn main(cli: &Cli) -> Result<()> {
output.push_str("pub const CATEGORIES: &[CheckCodePrefix] = &[");
output.push('\n');
for prefix in prefix_to_codes.keys() {
if prefix.chars().all(char::is_alphabetic) {
if prefix.chars().all(char::is_alphabetic)
&& !PREFIX_REDIRECTS.contains_key(&prefix.as_str())
{
output.push_str(&format!("CheckCodePrefix::{prefix},"));
output.push('\n');
}

View File

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

105
src/ast/branch_detection.rs Normal file
View File

@@ -0,0 +1,105 @@
use std::cmp::Ordering;
use rustc_hash::FxHashMap;
use rustpython_ast::ExcepthandlerKind::ExceptHandler;
use rustpython_ast::Stmt;
use rustpython_parser::ast::StmtKind;
use crate::ast::types::RefEquality;
/// Return the common ancestor of `left` and `right` below `stop`, or `None`.
fn common_ancestor<'a>(
left: &'a RefEquality<'a, Stmt>,
right: &'a RefEquality<'a, Stmt>,
stop: Option<&'a RefEquality<'a, Stmt>>,
depths: &'a FxHashMap<RefEquality<'a, Stmt>, usize>,
child_to_parent: &'a FxHashMap<RefEquality<'a, Stmt>, RefEquality<'a, Stmt>>,
) -> Option<&'a RefEquality<'a, Stmt>> {
if let Some(stop) = stop {
if left == stop || right == stop {
return None;
}
}
if left == right {
return Some(left);
}
let left_depth = depths.get(left)?;
let right_depth = depths.get(right)?;
match left_depth.cmp(right_depth) {
Ordering::Less => common_ancestor(
left,
child_to_parent.get(right)?,
stop,
depths,
child_to_parent,
),
Ordering::Equal => common_ancestor(
child_to_parent.get(left)?,
child_to_parent.get(right)?,
stop,
depths,
child_to_parent,
),
Ordering::Greater => common_ancestor(
child_to_parent.get(left)?,
right,
stop,
depths,
child_to_parent,
),
}
}
/// Return the alternative branches for a given node.
fn alternatives<'a>(node: &'a RefEquality<'a, Stmt>) -> Vec<Vec<RefEquality<'a, Stmt>>> {
match &node.0.node {
StmtKind::If { body, .. } => vec![body.iter().map(RefEquality).collect()],
StmtKind::Try {
body,
handlers,
orelse,
..
} => vec![body.iter().chain(orelse.iter()).map(RefEquality).collect()]
.into_iter()
.chain(handlers.iter().map(|handler| {
let ExceptHandler { body, .. } = &handler.node;
body.iter().map(RefEquality).collect()
}))
.collect(),
_ => vec![],
}
}
/// Return `true` if `node` is a descendent of any of the nodes in `ancestors`.
fn descendant_of<'a>(
node: &RefEquality<'a, Stmt>,
ancestors: &[RefEquality<'a, Stmt>],
stop: &RefEquality<'a, Stmt>,
depths: &FxHashMap<RefEquality<'a, Stmt>, usize>,
child_to_parent: &FxHashMap<RefEquality<'a, Stmt>, RefEquality<'a, Stmt>>,
) -> bool {
ancestors.iter().any(|ancestor| {
common_ancestor(node, ancestor, Some(stop), depths, child_to_parent).is_some()
})
}
/// Return `true` if `left` and `right` are on different branches of an `if` or
/// `try` statement.
pub fn different_forks<'a>(
left: &RefEquality<'a, Stmt>,
right: &RefEquality<'a, Stmt>,
depths: &FxHashMap<RefEquality<'a, Stmt>, usize>,
child_to_parent: &FxHashMap<RefEquality<'a, Stmt>, RefEquality<'a, Stmt>>,
) -> bool {
if let Some(ancestor) = common_ancestor(left, right, None, depths, child_to_parent) {
for items in alternatives(ancestor) {
let l = descendant_of(left, &items, ancestor, depths, child_to_parent);
let r = descendant_of(right, &items, ancestor, depths, child_to_parent);
if l ^ r {
return true;
}
}
}
false
}

View File

@@ -1,3 +1,4 @@
pub mod branch_detection;
pub mod cast;
pub mod function_type;
pub mod helpers;

View File

@@ -1,12 +1,15 @@
use rustc_hash::FxHashMap;
use rustpython_ast::{Cmpop, Located};
use rustpython_parser::ast::{Constant, Expr, ExprKind, Stmt, StmtKind};
use rustpython_parser::lexer;
use rustpython_parser::lexer::Tok;
use crate::ast::types::{BindingKind, Scope};
use crate::ast::types::{Binding, BindingKind, Scope};
use crate::ast::visitor;
use crate::ast::visitor::Visitor;
/// Extract the names bound to a given __all__ assignment.
pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
pub fn extract_all_names(stmt: &Stmt, scope: &Scope, bindings: &[Binding]) -> Vec<String> {
fn add_to_names(names: &mut Vec<String>, elts: &[Expr]) {
for elt in elts {
if let ExprKind::Constant {
@@ -23,8 +26,8 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
// Grab the existing bound __all__ values.
if let StmtKind::AugAssign { .. } = &stmt.node {
if let Some(binding) = scope.values.get("__all__") {
if let BindingKind::Export(existing) = &binding.kind {
if let Some(index) = scope.values.get("__all__") {
if let BindingKind::Export(existing) = &bindings[*index].kind {
names.extend_from_slice(existing);
}
}
@@ -69,6 +72,38 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
names
}
#[derive(Default)]
struct GlobalVisitor<'a> {
globals: FxHashMap<&'a str, &'a Stmt>,
}
impl<'a> Visitor<'a> for GlobalVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
match &stmt.node {
StmtKind::Global { names } => {
for name in names {
self.globals.insert(name, stmt);
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
// Don't recurse.
}
_ => visitor::walk_stmt(self, stmt),
}
}
}
/// Extract a map from global name to its last-defining `Stmt`.
pub fn extract_globals(body: &[Stmt]) -> FxHashMap<&str, &Stmt> {
let mut visitor = GlobalVisitor::default();
for stmt in body {
visitor.visit_stmt(stmt);
}
visitor.globals
}
/// Check if a node is parent of a conditional branch.
pub fn on_conditional_branch<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
parents.any(|parent| {

View File

@@ -32,23 +32,29 @@ impl Range {
#[derive(Debug)]
pub struct FunctionDef<'a> {
// Properties derived from StmtKind::FunctionDef.
pub name: &'a str,
pub args: &'a Arguments,
pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// pub returns: Option<&'a Expr>,
// pub type_comment: Option<&'a str>,
// Scope-specific properties.
// TODO(charlie): Create AsyncFunctionDef to mirror the AST.
pub async_: bool,
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
pub struct ClassDef<'a> {
// Properties derived from StmtKind::ClassDef.
pub name: &'a str,
pub bases: &'a [Expr],
pub keywords: &'a [Keyword],
// pub body: &'a [Stmt],
pub decorator_list: &'a [Expr],
// Scope-specific properties.
pub globals: FxHashMap<&'a str, &'a Stmt>,
}
#[derive(Debug)]
@@ -73,7 +79,11 @@ pub struct Scope<'a> {
pub kind: ScopeKind<'a>,
pub import_starred: bool,
pub uses_locals: bool,
pub values: FxHashMap<&'a str, Binding>,
/// A map from bound name to binding index.
pub values: FxHashMap<&'a str, usize>,
/// A list of (name, index) pairs for bindings that were overridden in the
/// scope.
pub overridden: Vec<(&'a str, usize)>,
}
impl<'a> Scope<'a> {
@@ -84,42 +94,120 @@ impl<'a> Scope<'a> {
import_starred: false,
uses_locals: false,
values: FxHashMap::default(),
overridden: Vec::new(),
}
}
}
#[derive(Clone, Debug)]
pub struct BindingContext {
pub defined_by: usize,
pub defined_in: Option<usize>,
}
#[derive(Clone, Debug)]
pub enum BindingKind {
Annotation,
Argument,
Assignment,
// TODO(charlie): This seems to be a catch-all.
Binding,
LoopVar,
Global,
Nonlocal,
Builtin,
ClassDefinition,
Definition,
FunctionDefinition,
Export(Vec<String>),
FutureImportation,
StarImportation(Option<usize>, Option<String>),
Importation(String, String, BindingContext),
FromImportation(String, String, BindingContext),
SubmoduleImportation(String, String, BindingContext),
Importation(String, String),
FromImportation(String, String),
SubmoduleImportation(String, String),
}
#[derive(Clone, Debug)]
pub struct Binding {
pub struct Binding<'a> {
pub kind: BindingKind,
pub range: Range,
/// The statement in which the `Binding` was defined.
pub source: Option<RefEquality<'a, Stmt>>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used.
pub used: Option<(usize, Range)>,
}
// Pyflakes defines the following binding hierarchy (via inheritance):
// Binding
// ExportBinding
// Annotation
// Argument
// Assignment
// NamedExprAssignment
// Definition
// FunctionDefinition
// ClassDefinition
// Builtin
// Importation
// SubmoduleImportation
// ImportationFrom
// StarImportation
// FutureImportation
impl<'a> Binding<'a> {
pub fn is_definition(&self) -> bool {
matches!(
self.kind,
BindingKind::ClassDefinition
| BindingKind::FunctionDefinition
| BindingKind::Builtin
| BindingKind::FutureImportation
| BindingKind::StarImportation(..)
| BindingKind::Importation(..)
| BindingKind::FromImportation(..)
| BindingKind::SubmoduleImportation(..)
)
}
pub fn redefines(&self, existing: &'a Binding) -> bool {
match &self.kind {
BindingKind::Importation(_, full_name) | BindingKind::FromImportation(_, full_name) => {
if let BindingKind::SubmoduleImportation(_, existing_full_name) = &existing.kind {
return full_name == existing_full_name;
}
}
BindingKind::SubmoduleImportation(_, full_name) => {
if let BindingKind::Importation(_, existing_full_name)
| BindingKind::FromImportation(_, existing_full_name)
| BindingKind::SubmoduleImportation(_, existing_full_name) = &existing.kind
{
return full_name == existing_full_name;
}
}
BindingKind::Annotation => {
return false;
}
BindingKind::FutureImportation => {
return false;
}
BindingKind::StarImportation(..) => {
return false;
}
_ => {}
}
existing.is_definition()
}
}
#[derive(Debug, Copy, Clone)]
pub struct RefEquality<'a, T>(pub &'a T);
impl<'a, T> std::hash::Hash for RefEquality<'a, T> {
fn hash<H>(&self, state: &mut H)
where
H: std::hash::Hasher,
{
(self.0 as *const T).hash(state);
}
}
impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq(&self, other: &RefEquality<'b, T>) -> bool {
std::ptr::eq(self.0, other.0)
}
}
impl<'a, T> Eq for RefEquality<'a, T> {}

View File

@@ -159,8 +159,8 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
orelse,
..
} => {
visitor.visit_expr(target);
visitor.visit_expr(iter);
visitor.visit_expr(target);
for stmt in body {
visitor.visit_stmt(stmt);
}
@@ -175,8 +175,8 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
orelse,
..
} => {
visitor.visit_expr(target);
visitor.visit_expr(iter);
visitor.visit_expr(target);
for stmt in body {
visitor.visit_stmt(stmt);
}

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::str::Lines;
use rustpython_ast::{Located, Location};
@@ -5,31 +6,26 @@ use rustpython_ast::{Located, Location};
use crate::ast::types::Range;
use crate::check_ast::Checker;
/// Extract the leading indentation from a line.
pub fn indentation<'a, T>(checker: &'a Checker, located: &'a Located<T>) -> Cow<'a, str> {
let range = Range::from_located(located);
checker.locator.slice_source_code_range(&Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.location.row(), range.location.column()),
})
}
/// Extract the leading words from a line of text.
pub fn leading_words(line: &str) -> String {
line.trim()
.chars()
.take_while(|char| char.is_alphanumeric() || char.is_whitespace())
.collect()
pub fn leading_words(line: &str) -> &str {
let line = line.trim();
line.find(|char: char| !char.is_alphanumeric() && !char.is_whitespace())
.map_or(line, |index| &line[..index])
}
/// Extract the leading whitespace from a line of text.
pub fn leading_space(line: &str) -> String {
line.chars()
.take_while(|char| char.is_whitespace())
.collect()
}
/// Extract the leading indentation from a line.
pub fn indentation<T>(checker: &Checker, located: &Located<T>) -> String {
let range = Range::from_located(located);
checker
.locator
.slice_source_code_range(&Range {
location: Location::new(range.location.row(), 0),
end_location: Location::new(range.location.row(), range.location.column()),
})
.to_string()
pub fn leading_space(line: &str) -> &str {
line.find(|char: char| !char.is_whitespace())
.map_or(line, |index| &line[..index])
}
/// Replace any non-whitespace characters from an indentation string.

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -7,7 +7,7 @@ use rustpython_parser::ast::Location;
use crate::ast::types::Range;
use crate::autofix::Fix;
use crate::checks::{Check, CheckCode, CheckKind, REDIRECTS};
use crate::checks::{Check, CheckCode, CheckKind, CODE_REDIRECTS};
use crate::noqa;
use crate::noqa::{is_file_exempt, Directive};
use crate::settings::Settings;
@@ -209,7 +209,7 @@ pub fn check_lines(
let mut invalid_codes = vec![];
let mut valid_codes = vec![];
for code in codes {
let code = REDIRECTS.get(code).map_or(code, AsRef::as_ref);
let code = CODE_REDIRECTS.get(code).map_or(code, AsRef::as_ref);
if matches.contains(&code) || settings.external.contains(code) {
valid_codes.push(code.to_string());
} else {

View File

@@ -87,22 +87,27 @@ pub enum CheckCode {
F706,
F707,
F722,
F811,
F821,
F822,
F823,
F831,
F841,
F842,
F901,
// pylint
PLC0414,
PLC2201,
PLC3002,
PLE0117,
PLE0118,
PLE1142,
PLR0206,
PLR0402,
PLR1701,
PLR1722,
PLW0120,
PLW0602,
// flake8-builtins
A001,
A002,
@@ -160,7 +165,7 @@ pub enum CheckCode {
// mccabe
C901,
// flake8-tidy-imports
I252,
TID252,
// flake8-return
RET501,
RET502,
@@ -242,6 +247,7 @@ pub enum CheckCode {
D214,
D215,
D300,
D301,
D400,
D402,
D403,
@@ -400,9 +406,9 @@ impl CheckCategory {
CheckCategory::Flake8Print => vec![CheckCodePrefix::T20],
CheckCategory::Flake8Quotes => vec![CheckCodePrefix::Q],
CheckCategory::Flake8Return => vec![CheckCodePrefix::RET],
CheckCategory::Flake8TidyImports => vec![CheckCodePrefix::I25],
CheckCategory::Flake8TidyImports => vec![CheckCodePrefix::TID],
CheckCategory::Flake8UnusedArguments => vec![CheckCodePrefix::ARG],
CheckCategory::Isort => vec![CheckCodePrefix::I00],
CheckCategory::Isort => vec![CheckCodePrefix::I],
CheckCategory::McCabe => vec![CheckCodePrefix::C90],
CheckCategory::PEP8Naming => vec![CheckCodePrefix::N],
CheckCategory::Pycodestyle => vec![CheckCodePrefix::E, CheckCodePrefix::W],
@@ -616,6 +622,7 @@ pub enum CheckKind {
PercentFormatStarRequiresSequence,
PercentFormatUnsupportedFormatCharacter(char),
RaiseNotImplemented,
RedefinedWhileUnused(String, usize),
ReturnOutsideFunction,
StringDotFormatExtraNamedArguments(Vec<String>),
StringDotFormatExtraPositionalArguments(Vec<String>),
@@ -625,20 +632,24 @@ pub enum CheckKind {
TwoStarredExpressions,
UndefinedExport(String),
UndefinedLocal(String),
UnusedAnnotation(String),
UndefinedName(String),
UnusedImport(String, bool),
UnusedVariable(String),
YieldOutsideFunction(DeferralKeyword),
// pylint
ConsiderMergingIsinstance(String, Vec<String>),
UselessImportAlias,
MisplacedComparisonConstant(String),
UnnecessaryDirectLambdaCall,
PropertyWithParameters,
ConsiderUsingFromImport(String, String),
AwaitOutsideAsync,
ConsiderMergingIsinstance(String, Vec<String>),
ConsiderUsingFromImport(String, String),
GlobalVariableNotAssigned(String),
MisplacedComparisonConstant(String),
NonlocalWithoutBinding(String),
PropertyWithParameters,
UnnecessaryDirectLambdaCall,
UseSysExit(String),
UsedPriorGlobalDeclaration(String, usize),
UselessElseOnLoop,
ConsiderUsingSysExit,
UselessImportAlias,
// flake8-builtins
BuiltinVariableShadowing(String),
BuiltinArgumentShadowing(String),
@@ -794,6 +805,7 @@ pub enum CheckKind {
SectionUnderlineMatchesSectionLength(String),
SectionUnderlineNotOverIndented(String),
SkipDocstring,
UsesRPrefixForBackslashedContent,
UsesTripleQuotes,
// pep8-naming
InvalidClassName(String),
@@ -932,16 +944,20 @@ impl CheckCode {
CheckCode::F706 => CheckKind::ReturnOutsideFunction,
CheckCode::F707 => CheckKind::DefaultExceptNotLast,
CheckCode::F722 => CheckKind::ForwardAnnotationSyntaxError("...".to_string()),
CheckCode::F811 => CheckKind::RedefinedWhileUnused("...".to_string(), 1),
CheckCode::F821 => CheckKind::UndefinedName("...".to_string()),
CheckCode::F822 => CheckKind::UndefinedExport("...".to_string()),
CheckCode::F823 => CheckKind::UndefinedLocal("...".to_string()),
CheckCode::F831 => CheckKind::DuplicateArgumentName,
CheckCode::F841 => CheckKind::UnusedVariable("...".to_string()),
CheckCode::F842 => CheckKind::UnusedAnnotation("...".to_string()),
CheckCode::F901 => CheckKind::RaiseNotImplemented,
// pylint
CheckCode::PLC0414 => CheckKind::UselessImportAlias,
CheckCode::PLC2201 => CheckKind::MisplacedComparisonConstant("...".to_string()),
CheckCode::PLC3002 => CheckKind::UnnecessaryDirectLambdaCall,
CheckCode::PLE0117 => CheckKind::NonlocalWithoutBinding("...".to_string()),
CheckCode::PLE0118 => CheckKind::UsedPriorGlobalDeclaration("...".to_string(), 1),
CheckCode::PLE1142 => CheckKind::AwaitOutsideAsync,
CheckCode::PLR0402 => {
CheckKind::ConsiderUsingFromImport("...".to_string(), "...".to_string())
@@ -950,8 +966,9 @@ impl CheckCode {
CheckCode::PLR1701 => {
CheckKind::ConsiderMergingIsinstance("...".to_string(), vec!["...".to_string()])
}
CheckCode::PLR1722 => CheckKind::ConsiderUsingSysExit,
CheckCode::PLR1722 => CheckKind::UseSysExit("exit".to_string()),
CheckCode::PLW0120 => CheckKind::UselessElseOnLoop,
CheckCode::PLW0602 => CheckKind::GlobalVariableNotAssigned("...".to_string()),
// flake8-builtins
CheckCode::A001 => CheckKind::BuiltinVariableShadowing("...".to_string()),
CheckCode::A002 => CheckKind::BuiltinArgumentShadowing("...".to_string()),
@@ -1022,7 +1039,7 @@ impl CheckCode {
// flake8-debugger
CheckCode::T100 => CheckKind::Debugger(DebuggerUsingType::Import("...".to_string())),
// flake8-tidy-imports
CheckCode::I252 => CheckKind::BannedRelativeImport(Strictness::All),
CheckCode::TID252 => CheckKind::BannedRelativeImport(Strictness::All),
// flake8-return
CheckCode::RET501 => CheckKind::UnnecessaryReturnNone,
CheckCode::RET502 => CheckKind::ImplicitReturnValue,
@@ -1109,6 +1126,7 @@ impl CheckCode {
CheckCode::D214 => CheckKind::SectionNotOverIndented("Returns".to_string()),
CheckCode::D215 => CheckKind::SectionUnderlineNotOverIndented("Returns".to_string()),
CheckCode::D300 => CheckKind::UsesTripleQuotes,
CheckCode::D301 => CheckKind::UsesRPrefixForBackslashedContent,
CheckCode::D400 => CheckKind::EndsInPeriod,
CheckCode::D402 => CheckKind::NoSignature,
CheckCode::D403 => CheckKind::FirstLineCapitalized,
@@ -1289,6 +1307,7 @@ impl CheckCode {
CheckCode::D214 => CheckCategory::Pydocstyle,
CheckCode::D215 => CheckCategory::Pydocstyle,
CheckCode::D300 => CheckCategory::Pydocstyle,
CheckCode::D301 => CheckCategory::Pydocstyle,
CheckCode::D400 => CheckCategory::Pydocstyle,
CheckCode::D402 => CheckCategory::Pydocstyle,
CheckCode::D403 => CheckCategory::Pydocstyle,
@@ -1359,17 +1378,19 @@ impl CheckCode {
CheckCode::F706 => CheckCategory::Pyflakes,
CheckCode::F707 => CheckCategory::Pyflakes,
CheckCode::F722 => CheckCategory::Pyflakes,
CheckCode::F811 => CheckCategory::Pyflakes,
CheckCode::F821 => CheckCategory::Pyflakes,
CheckCode::F822 => CheckCategory::Pyflakes,
CheckCode::F823 => CheckCategory::Pyflakes,
CheckCode::F831 => CheckCategory::Pyflakes,
CheckCode::F841 => CheckCategory::Pyflakes,
CheckCode::F842 => CheckCategory::Pyflakes,
CheckCode::F901 => CheckCategory::Pyflakes,
CheckCode::FBT001 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT002 => CheckCategory::Flake8BooleanTrap,
CheckCode::FBT003 => CheckCategory::Flake8BooleanTrap,
CheckCode::I001 => CheckCategory::Isort,
CheckCode::I252 => CheckCategory::Flake8TidyImports,
CheckCode::TID252 => CheckCategory::Flake8TidyImports,
CheckCode::ICN001 => CheckCategory::Flake8ImportConventions,
CheckCode::N801 => CheckCategory::PEP8Naming,
CheckCode::N802 => CheckCategory::PEP8Naming,
@@ -1390,12 +1411,15 @@ impl CheckCode {
CheckCode::PLC0414 => CheckCategory::Pylint,
CheckCode::PLC2201 => CheckCategory::Pylint,
CheckCode::PLC3002 => CheckCategory::Pylint,
CheckCode::PLE0117 => CheckCategory::Pylint,
CheckCode::PLE0118 => CheckCategory::Pylint,
CheckCode::PLE1142 => CheckCategory::Pylint,
CheckCode::PLR0206 => CheckCategory::Pylint,
CheckCode::PLR0402 => CheckCategory::Pylint,
CheckCode::PLR1701 => CheckCategory::Pylint,
CheckCode::PLR1722 => CheckCategory::Pylint,
CheckCode::PLW0120 => CheckCategory::Pylint,
CheckCode::PLW0602 => CheckCategory::Pylint,
CheckCode::Q000 => CheckCategory::Flake8Quotes,
CheckCode::Q001 => CheckCategory::Flake8Quotes,
CheckCode::Q002 => CheckCategory::Flake8Quotes,
@@ -1508,23 +1532,28 @@ impl CheckKind {
CheckKind::TypeComparison => &CheckCode::E721,
CheckKind::UndefinedExport(_) => &CheckCode::F822,
CheckKind::UndefinedLocal(_) => &CheckCode::F823,
CheckKind::RedefinedWhileUnused(..) => &CheckCode::F811,
CheckKind::UndefinedName(_) => &CheckCode::F821,
CheckKind::UnusedImport(..) => &CheckCode::F401,
CheckKind::UnusedVariable(_) => &CheckCode::F841,
CheckKind::UnusedAnnotation(_) => &CheckCode::F842,
CheckKind::YieldOutsideFunction(_) => &CheckCode::F704,
// pycodestyle warnings
CheckKind::NoNewLineAtEndOfFile => &CheckCode::W292,
CheckKind::InvalidEscapeSequence(_) => &CheckCode::W605,
// pylint
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
CheckKind::AwaitOutsideAsync => &CheckCode::PLE1142,
CheckKind::ConsiderMergingIsinstance(..) => &CheckCode::PLR1701,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::ConsiderUsingFromImport(..) => &CheckCode::PLR0402,
CheckKind::ConsiderUsingSysExit => &CheckCode::PLR1722,
CheckKind::GlobalVariableNotAssigned(..) => &CheckCode::PLW0602,
CheckKind::MisplacedComparisonConstant(..) => &CheckCode::PLC2201,
CheckKind::PropertyWithParameters => &CheckCode::PLR0206,
CheckKind::UnnecessaryDirectLambdaCall => &CheckCode::PLC3002,
CheckKind::UseSysExit(_) => &CheckCode::PLR1722,
CheckKind::NonlocalWithoutBinding(..) => &CheckCode::PLE0117,
CheckKind::UsedPriorGlobalDeclaration(..) => &CheckCode::PLE0118,
CheckKind::UselessElseOnLoop => &CheckCode::PLW0120,
CheckKind::UselessImportAlias => &CheckCode::PLC0414,
// flake8-builtins
CheckKind::BuiltinVariableShadowing(_) => &CheckCode::A001,
CheckKind::BuiltinArgumentShadowing(_) => &CheckCode::A002,
@@ -1580,7 +1609,7 @@ impl CheckKind {
// flake8-debugger
CheckKind::Debugger(_) => &CheckCode::T100,
// flake8-tidy-imports
CheckKind::BannedRelativeImport(_) => &CheckCode::I252,
CheckKind::BannedRelativeImport(_) => &CheckCode::TID252,
// flake8-return
CheckKind::UnnecessaryReturnNone => &CheckCode::RET501,
CheckKind::ImplicitReturnValue => &CheckCode::RET502,
@@ -1680,6 +1709,7 @@ impl CheckKind {
CheckKind::SectionUnderlineMatchesSectionLength(_) => &CheckCode::D409,
CheckKind::SectionUnderlineNotOverIndented(_) => &CheckCode::D215,
CheckKind::SkipDocstring => &CheckCode::D418,
CheckKind::UsesRPrefixForBackslashedContent => &CheckCode::D301,
CheckKind::UsesTripleQuotes => &CheckCode::D300,
// pep8-naming
CheckKind::InvalidClassName(_) => &CheckCode::N801,
@@ -1846,6 +1876,9 @@ impl CheckKind {
CheckKind::RaiseNotImplemented => {
"`raise NotImplemented` should be `raise NotImplementedError`".to_string()
}
CheckKind::RedefinedWhileUnused(name, line) => {
format!("Redefinition of unused `{name}` from line {line}")
}
CheckKind::ReturnOutsideFunction => {
"`return` statement outside of a function/method".to_string()
}
@@ -1894,6 +1927,9 @@ impl CheckKind {
CheckKind::UndefinedName(name) => {
format!("Undefined name `{name}`")
}
CheckKind::UnusedAnnotation(name) => {
format!("Local variable `{name}` is annotated but never used")
}
CheckKind::UnusedImport(name, ignore_init) => {
if *ignore_init {
format!(
@@ -1921,10 +1957,13 @@ impl CheckKind {
}
CheckKind::ConsiderMergingIsinstance(obj, types) => {
let types = types.join(", ");
format!("Consider merging these isinstance calls: `isinstance({obj}, ({types}))`")
format!("Merge these isinstance calls: `isinstance({obj}, ({types}))`")
}
CheckKind::MisplacedComparisonConstant(comprison) => {
format!("Comparison should be {comprison}")
CheckKind::MisplacedComparisonConstant(comparison) => {
format!("Comparison should be {comparison}")
}
CheckKind::NonlocalWithoutBinding(name) => {
format!("Nonlocal name `{name}` found without binding")
}
CheckKind::UnnecessaryDirectLambdaCall => "Lambda expression called directly. Execute \
the expression inline instead."
@@ -1933,7 +1972,13 @@ impl CheckKind {
"Cannot have defined parameters for properties".to_string()
}
CheckKind::ConsiderUsingFromImport(module, name) => {
format!("Consider using `from {module} import {name}`")
format!("Use `from {module} import {name}` in lieu of alias")
}
CheckKind::UsedPriorGlobalDeclaration(name, line) => {
format!("Name `{name}` is used prior to global declaration on line {line}")
}
CheckKind::GlobalVariableNotAssigned(name) => {
format!("Using global for `{name}` but no assignment is done")
}
CheckKind::AwaitOutsideAsync => {
"`await` should be used within an async function".to_string()
@@ -1941,7 +1986,7 @@ impl CheckKind {
CheckKind::UselessElseOnLoop => "Else clause on loop without a break statement, \
remove the else and de-indent all the code inside it"
.to_string(),
CheckKind::ConsiderUsingSysExit => "Consider using `sys.exit()`".to_string(),
CheckKind::UseSysExit(name) => format!("Use `sys.exit()` instead of `{name}`"),
// flake8-builtins
CheckKind::BuiltinVariableShadowing(name) => {
format!("Variable `{name}` is shadowing a python builtin")
@@ -2332,6 +2377,9 @@ impl CheckKind {
CheckKind::FirstLineCapitalized => {
"First word of the first line should be properly capitalized".to_string()
}
CheckKind::UsesRPrefixForBackslashedContent => {
r#"Use r""" if any backslashes in a docstring"#.to_string()
}
CheckKind::UsesTripleQuotes => r#"Use """triple double quotes""""#.to_string(),
CheckKind::MultiLineSummaryFirstLine => {
"Multi-line docstring summary should start at the first line".to_string()
@@ -2599,7 +2647,6 @@ impl CheckKind {
| CheckKind::BlankLineBeforeSection(..)
| CheckKind::CapitalizeSectionName(..)
| CheckKind::CommentedOutCode
| CheckKind::ConsiderUsingSysExit
| CheckKind::ConvertNamedTupleFunctionalToClass(..)
| CheckKind::ConvertTypedDictFunctionalToClass(..)
| CheckKind::DashedUnderlineAfterSection(..)
@@ -2665,6 +2712,7 @@ impl CheckKind {
| CheckKind::UnusedNOQA(..)
| CheckKind::UsePEP585Annotation(..)
| CheckKind::UsePEP604Annotation
| CheckKind::UseSysExit(..)
| CheckKind::UselessImportAlias
| CheckKind::UselessMetaclassType
| CheckKind::UselessObjectInheritance(..)
@@ -2696,7 +2744,7 @@ impl Check {
}
/// A hash map from deprecated to latest `CheckCode`.
pub static REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
pub static CODE_REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U001", CheckCode::UP001),
@@ -2713,6 +2761,25 @@ pub static REDIRECTS: Lazy<FxHashMap<&'static str, CheckCode>> = Lazy::new(|| {
("U013", CheckCode::UP013),
("U014", CheckCode::UP014),
("U015", CheckCode::UP015),
// TODO(charlie): Remove by 2023-02-01.
("I252", CheckCode::TID252),
("M001", CheckCode::RUF100),
])
});
/// A hash map from deprecated `CheckCodePrefix` to latest `CheckCodePrefix`.
pub static PREFIX_REDIRECTS: Lazy<FxHashMap<&'static str, &'static str>> = Lazy::new(|| {
FxHashMap::from_iter([
// TODO(charlie): Remove by 2023-01-01.
("U", "UP"),
("U0", "UP0"),
("U00", "UP00"),
("U01", "UP01"),
// TODO(charlie): Remove by 2023-02-01.
("I2", "TID2"),
("I25", "TID25"),
("M", "RUF100"),
("M0", "RUF100"),
])
});

View File

@@ -139,6 +139,7 @@ pub enum CheckCodePrefix {
D3,
D30,
D300,
D301,
D4,
D40,
D400,
@@ -243,6 +244,8 @@ pub enum CheckCodePrefix {
F72,
F722,
F8,
F81,
F811,
F82,
F821,
F822,
@@ -251,6 +254,7 @@ pub enum CheckCodePrefix {
F831,
F84,
F841,
F842,
F9,
F90,
F901,
@@ -271,6 +275,9 @@ pub enum CheckCodePrefix {
ICN0,
ICN00,
ICN001,
M,
M0,
M001,
N,
N8,
N80,
@@ -308,6 +315,11 @@ pub enum CheckCodePrefix {
PLC300,
PLC3002,
PLE,
PLE0,
PLE01,
PLE011,
PLE0117,
PLE0118,
PLE1,
PLE11,
PLE114,
@@ -331,6 +343,9 @@ pub enum CheckCodePrefix {
PLW01,
PLW012,
PLW0120,
PLW06,
PLW060,
PLW0602,
Q,
Q0,
Q00,
@@ -375,6 +390,10 @@ pub enum CheckCodePrefix {
T20,
T201,
T203,
TID,
TID2,
TID25,
TID252,
U,
U0,
U00,
@@ -752,6 +771,7 @@ impl CheckCodePrefix {
CheckCode::D214,
CheckCode::D215,
CheckCode::D300,
CheckCode::D301,
CheckCode::D400,
CheckCode::D402,
CheckCode::D403,
@@ -854,9 +874,10 @@ impl CheckCodePrefix {
CheckCodePrefix::D213 => vec![CheckCode::D213],
CheckCodePrefix::D214 => vec![CheckCode::D214],
CheckCodePrefix::D215 => vec![CheckCode::D215],
CheckCodePrefix::D3 => vec![CheckCode::D300],
CheckCodePrefix::D30 => vec![CheckCode::D300],
CheckCodePrefix::D3 => vec![CheckCode::D300, CheckCode::D301],
CheckCodePrefix::D30 => vec![CheckCode::D300, CheckCode::D301],
CheckCodePrefix::D300 => vec![CheckCode::D300],
CheckCodePrefix::D301 => vec![CheckCode::D301],
CheckCodePrefix::D4 => vec![
CheckCode::D400,
CheckCode::D402,
@@ -1019,11 +1040,13 @@ impl CheckCodePrefix {
CheckCode::F706,
CheckCode::F707,
CheckCode::F722,
CheckCode::F811,
CheckCode::F821,
CheckCode::F822,
CheckCode::F823,
CheckCode::F831,
CheckCode::F841,
CheckCode::F842,
CheckCode::F901,
],
CheckCodePrefix::F4 => vec![
@@ -1151,20 +1174,25 @@ impl CheckCodePrefix {
CheckCodePrefix::F72 => vec![CheckCode::F722],
CheckCodePrefix::F722 => vec![CheckCode::F722],
CheckCodePrefix::F8 => vec![
CheckCode::F811,
CheckCode::F821,
CheckCode::F822,
CheckCode::F823,
CheckCode::F831,
CheckCode::F841,
CheckCode::F842,
],
CheckCodePrefix::F81 => vec![CheckCode::F811],
CheckCodePrefix::F811 => vec![CheckCode::F811],
CheckCodePrefix::F82 => vec![CheckCode::F821, CheckCode::F822, CheckCode::F823],
CheckCodePrefix::F821 => vec![CheckCode::F821],
CheckCodePrefix::F822 => vec![CheckCode::F822],
CheckCodePrefix::F823 => vec![CheckCode::F823],
CheckCodePrefix::F83 => vec![CheckCode::F831],
CheckCodePrefix::F831 => vec![CheckCode::F831],
CheckCodePrefix::F84 => vec![CheckCode::F841],
CheckCodePrefix::F84 => vec![CheckCode::F841, CheckCode::F842],
CheckCodePrefix::F841 => vec![CheckCode::F841],
CheckCodePrefix::F842 => vec![CheckCode::F842],
CheckCodePrefix::F9 => vec![CheckCode::F901],
CheckCodePrefix::F90 => vec![CheckCode::F901],
CheckCodePrefix::F901 => vec![CheckCode::F901],
@@ -1174,17 +1202,68 @@ impl CheckCodePrefix {
CheckCodePrefix::FBT001 => vec![CheckCode::FBT001],
CheckCodePrefix::FBT002 => vec![CheckCode::FBT002],
CheckCodePrefix::FBT003 => vec![CheckCode::FBT003],
CheckCodePrefix::I => vec![CheckCode::I252, CheckCode::I001],
CheckCodePrefix::I => vec![CheckCode::I001],
CheckCodePrefix::I0 => vec![CheckCode::I001],
CheckCodePrefix::I00 => vec![CheckCode::I001],
CheckCodePrefix::I001 => vec![CheckCode::I001],
CheckCodePrefix::I2 => vec![CheckCode::I252],
CheckCodePrefix::I25 => vec![CheckCode::I252],
CheckCodePrefix::I252 => vec![CheckCode::I252],
CheckCodePrefix::I2 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`I2` has been remapped to `TID2`".bold()
);
vec![CheckCode::TID252]
}
CheckCodePrefix::I25 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`I25` has been remapped to `TID25`".bold()
);
vec![CheckCode::TID252]
}
CheckCodePrefix::I252 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`I252` has been remapped to `TID252`".bold()
);
vec![CheckCode::TID252]
}
CheckCodePrefix::ICN => vec![CheckCode::ICN001],
CheckCodePrefix::ICN0 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN00 => vec![CheckCode::ICN001],
CheckCodePrefix::ICN001 => vec![CheckCode::ICN001],
CheckCodePrefix::M => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`M` has been remapped to `RUF100`".bold()
);
vec![CheckCode::RUF100]
}
CheckCodePrefix::M0 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`M0` has been remapped to `RUF100`".bold()
);
vec![CheckCode::RUF100]
}
CheckCodePrefix::M001 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`M001` has been remapped to `RUF100`".bold()
);
vec![CheckCode::RUF100]
}
CheckCodePrefix::N => vec![
CheckCode::N801,
CheckCode::N802,
@@ -1272,7 +1351,14 @@ impl CheckCodePrefix {
CheckCodePrefix::PLC30 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLC300 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLC3002 => vec![CheckCode::PLC3002],
CheckCodePrefix::PLE => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE => {
vec![CheckCode::PLE0117, CheckCode::PLE0118, CheckCode::PLE1142]
}
CheckCodePrefix::PLE0 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
CheckCodePrefix::PLE01 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
CheckCodePrefix::PLE011 => vec![CheckCode::PLE0117, CheckCode::PLE0118],
CheckCodePrefix::PLE0117 => vec![CheckCode::PLE0117],
CheckCodePrefix::PLE0118 => vec![CheckCode::PLE0118],
CheckCodePrefix::PLE1 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE11 => vec![CheckCode::PLE1142],
CheckCodePrefix::PLE114 => vec![CheckCode::PLE1142],
@@ -1296,11 +1382,14 @@ impl CheckCodePrefix {
CheckCodePrefix::PLR1701 => vec![CheckCode::PLR1701],
CheckCodePrefix::PLR172 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLR1722 => vec![CheckCode::PLR1722],
CheckCodePrefix::PLW => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW => vec![CheckCode::PLW0120, CheckCode::PLW0602],
CheckCodePrefix::PLW0 => vec![CheckCode::PLW0120, CheckCode::PLW0602],
CheckCodePrefix::PLW01 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW012 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW0120 => vec![CheckCode::PLW0120],
CheckCodePrefix::PLW06 => vec![CheckCode::PLW0602],
CheckCodePrefix::PLW060 => vec![CheckCode::PLW0602],
CheckCodePrefix::PLW0602 => vec![CheckCode::PLW0602],
CheckCodePrefix::Q => vec![
CheckCode::Q000,
CheckCode::Q001,
@@ -1413,54 +1502,82 @@ impl CheckCodePrefix {
CheckCodePrefix::T20 => vec![CheckCode::T201, CheckCode::T203],
CheckCodePrefix::T201 => vec![CheckCode::T201],
CheckCodePrefix::T203 => vec![CheckCode::T203],
CheckCodePrefix::U => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U0 => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U00 => vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
],
CheckCodePrefix::TID => vec![CheckCode::TID252],
CheckCodePrefix::TID2 => vec![CheckCode::TID252],
CheckCodePrefix::TID25 => vec![CheckCode::TID252],
CheckCodePrefix::TID252 => vec![CheckCode::TID252],
CheckCodePrefix::U => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U` has been remapped to `UP`".bold()
);
vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
]
}
CheckCodePrefix::U0 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U0` has been remapped to `UP0`".bold()
);
vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
]
}
CheckCodePrefix::U00 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U00` has been remapped to `UP00`".bold()
);
vec![
CheckCode::UP001,
CheckCode::UP003,
CheckCode::UP004,
CheckCode::UP005,
CheckCode::UP006,
CheckCode::UP007,
CheckCode::UP008,
CheckCode::UP009,
]
}
CheckCodePrefix::U001 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U001` has been renamed to `UP001`".bold()
"`U001` has been remapped to `UP001`".bold()
);
vec![CheckCode::UP001]
}
@@ -1469,7 +1586,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U003` has been renamed to `UP003`".bold()
"`U003` has been remapped to `UP003`".bold()
);
vec![CheckCode::UP003]
}
@@ -1478,7 +1595,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U004` has been renamed to `UP004`".bold()
"`U004` has been remapped to `UP004`".bold()
);
vec![CheckCode::UP004]
}
@@ -1487,7 +1604,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U005` has been renamed to `UP005`".bold()
"`U005` has been remapped to `UP005`".bold()
);
vec![CheckCode::UP005]
}
@@ -1496,7 +1613,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U006` has been renamed to `UP006`".bold()
"`U006` has been remapped to `UP006`".bold()
);
vec![CheckCode::UP006]
}
@@ -1505,7 +1622,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U007` has been renamed to `UP007`".bold()
"`U007` has been remapped to `UP007`".bold()
);
vec![CheckCode::UP007]
}
@@ -1514,7 +1631,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U008` has been renamed to `UP008`".bold()
"`U008` has been remapped to `UP008`".bold()
);
vec![CheckCode::UP008]
}
@@ -1523,24 +1640,32 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U009` has been renamed to `UP009`".bold()
"`U009` has been remapped to `UP009`".bold()
);
vec![CheckCode::UP009]
}
CheckCodePrefix::U01 => vec![
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
],
CheckCodePrefix::U01 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U01` has been remapped to `UP01`".bold()
);
vec![
CheckCode::UP010,
CheckCode::UP011,
CheckCode::UP012,
CheckCode::UP013,
CheckCode::UP014,
CheckCode::UP015,
]
}
CheckCodePrefix::U010 => {
eprintln!(
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U010` has been renamed to `UP010`".bold()
"`U010` has been remapped to `UP010`".bold()
);
vec![CheckCode::UP010]
}
@@ -1549,7 +1674,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U011` has been renamed to `UP011`".bold()
"`U011` has been remapped to `UP011`".bold()
);
vec![CheckCode::UP011]
}
@@ -1558,7 +1683,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U012` has been renamed to `UP012`".bold()
"`U012` has been remapped to `UP012`".bold()
);
vec![CheckCode::UP012]
}
@@ -1567,7 +1692,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U013` has been renamed to `UP013`".bold()
"`U013` has been remapped to `UP013`".bold()
);
vec![CheckCode::UP013]
}
@@ -1576,7 +1701,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U014` has been renamed to `UP014`".bold()
"`U014` has been remapped to `UP014`".bold()
);
vec![CheckCode::UP014]
}
@@ -1585,7 +1710,7 @@ impl CheckCodePrefix {
"{}{} {}",
"warning".yellow().bold(),
":".bold(),
"`U015` has been renamed to `UP015`".bold()
"`U015` has been remapped to `UP015`".bold()
);
vec![CheckCode::UP015]
}
@@ -1835,6 +1960,7 @@ impl CheckCodePrefix {
CheckCodePrefix::D3 => SuffixLength::One,
CheckCodePrefix::D30 => SuffixLength::Two,
CheckCodePrefix::D300 => SuffixLength::Three,
CheckCodePrefix::D301 => SuffixLength::Three,
CheckCodePrefix::D4 => SuffixLength::One,
CheckCodePrefix::D40 => SuffixLength::Two,
CheckCodePrefix::D400 => SuffixLength::Three,
@@ -1939,6 +2065,8 @@ impl CheckCodePrefix {
CheckCodePrefix::F72 => SuffixLength::Two,
CheckCodePrefix::F722 => SuffixLength::Three,
CheckCodePrefix::F8 => SuffixLength::One,
CheckCodePrefix::F81 => SuffixLength::Two,
CheckCodePrefix::F811 => SuffixLength::Three,
CheckCodePrefix::F82 => SuffixLength::Two,
CheckCodePrefix::F821 => SuffixLength::Three,
CheckCodePrefix::F822 => SuffixLength::Three,
@@ -1947,6 +2075,7 @@ impl CheckCodePrefix {
CheckCodePrefix::F831 => SuffixLength::Three,
CheckCodePrefix::F84 => SuffixLength::Two,
CheckCodePrefix::F841 => SuffixLength::Three,
CheckCodePrefix::F842 => SuffixLength::Three,
CheckCodePrefix::F9 => SuffixLength::One,
CheckCodePrefix::F90 => SuffixLength::Two,
CheckCodePrefix::F901 => SuffixLength::Three,
@@ -1967,6 +2096,9 @@ impl CheckCodePrefix {
CheckCodePrefix::ICN0 => SuffixLength::One,
CheckCodePrefix::ICN00 => SuffixLength::Two,
CheckCodePrefix::ICN001 => SuffixLength::Three,
CheckCodePrefix::M => SuffixLength::Zero,
CheckCodePrefix::M0 => SuffixLength::One,
CheckCodePrefix::M001 => SuffixLength::Three,
CheckCodePrefix::N => SuffixLength::Zero,
CheckCodePrefix::N8 => SuffixLength::One,
CheckCodePrefix::N80 => SuffixLength::Two,
@@ -2004,6 +2136,11 @@ impl CheckCodePrefix {
CheckCodePrefix::PLC300 => SuffixLength::Three,
CheckCodePrefix::PLC3002 => SuffixLength::Four,
CheckCodePrefix::PLE => SuffixLength::Zero,
CheckCodePrefix::PLE0 => SuffixLength::One,
CheckCodePrefix::PLE01 => SuffixLength::Two,
CheckCodePrefix::PLE011 => SuffixLength::Three,
CheckCodePrefix::PLE0117 => SuffixLength::Four,
CheckCodePrefix::PLE0118 => SuffixLength::Four,
CheckCodePrefix::PLE1 => SuffixLength::One,
CheckCodePrefix::PLE11 => SuffixLength::Two,
CheckCodePrefix::PLE114 => SuffixLength::Three,
@@ -2027,6 +2164,9 @@ impl CheckCodePrefix {
CheckCodePrefix::PLW01 => SuffixLength::Two,
CheckCodePrefix::PLW012 => SuffixLength::Three,
CheckCodePrefix::PLW0120 => SuffixLength::Four,
CheckCodePrefix::PLW06 => SuffixLength::Two,
CheckCodePrefix::PLW060 => SuffixLength::Three,
CheckCodePrefix::PLW0602 => SuffixLength::Four,
CheckCodePrefix::Q => SuffixLength::Zero,
CheckCodePrefix::Q0 => SuffixLength::One,
CheckCodePrefix::Q00 => SuffixLength::Two,
@@ -2071,6 +2211,10 @@ impl CheckCodePrefix {
CheckCodePrefix::T20 => SuffixLength::Two,
CheckCodePrefix::T201 => SuffixLength::Three,
CheckCodePrefix::T203 => SuffixLength::Three,
CheckCodePrefix::TID => SuffixLength::Zero,
CheckCodePrefix::TID2 => SuffixLength::One,
CheckCodePrefix::TID25 => SuffixLength::Two,
CheckCodePrefix::TID252 => SuffixLength::Three,
CheckCodePrefix::U => SuffixLength::Zero,
CheckCodePrefix::U0 => SuffixLength::One,
CheckCodePrefix::U00 => SuffixLength::Two,
@@ -2160,7 +2304,7 @@ pub const CATEGORIES: &[CheckCodePrefix] = &[
CheckCodePrefix::RUF,
CheckCodePrefix::S,
CheckCodePrefix::T,
CheckCodePrefix::U,
CheckCodePrefix::TID,
CheckCodePrefix::UP,
CheckCodePrefix::W,
CheckCodePrefix::YTT,

View File

@@ -1,5 +1,9 @@
pub const TRIPLE_QUOTE_PREFIXES: &[&str] = &[
"ur\"\"\"", "ur'''", "u\"\"\"", "u'''", "r\"\"\"", "r'''", "\"\"\"", "'''",
"ur\"\"\"", "ur'''", "u\"\"\"", "u'''", "r\"\"\"", "r'''", "UR\"\"\"", "UR'''", "Ur\"\"\"",
"Ur'''", "U\"\"\"", "U'''", "uR\"\"\"", "uR'''", "R\"\"\"", "R'''", "\"\"\"", "'''",
];
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &["ur\"", "ur'", "u\"", "u'", "r\"", "r'", "\"", "'"];
pub const SINGLE_QUOTE_PREFIXES: &[&str] = &[
"ur\"", "ur'", "u\"", "u'", "r\"", "r'", "ur\"", "ur'", "u\"", "u'", "r\"", "r'", "UR\"",
"UR'", "Ur\"", "Ur'", "U\"", "U'", "uR\"", "uR'", "R\"", "R'", "\"", "'",
];

View File

@@ -1,6 +1,8 @@
use std::borrow::Cow;
use rustpython_ast::{Expr, Stmt};
#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum DefinitionKind<'a> {
Module,
Package,
@@ -17,6 +19,15 @@ pub struct Definition<'a> {
pub docstring: Option<&'a Expr>,
}
#[derive(Debug)]
pub struct Docstring<'a> {
pub kind: DefinitionKind<'a>,
pub expr: &'a Expr,
pub contents: &'a Cow<'a, str>,
pub body: &'a str,
pub indentation: &'a Cow<'a, str>,
}
pub enum Documentable {
Class,
Function,

View File

@@ -3,7 +3,7 @@ use crate::docstrings::styles::SectionStyle;
#[derive(Debug)]
pub(crate) struct SectionContext<'a> {
pub(crate) section_name: String,
pub(crate) section_name: &'a str,
pub(crate) previous_line: &'a str,
pub(crate) line: &'a str,
pub(crate) following_lines: &'a [&'a str],
@@ -22,7 +22,7 @@ fn is_docstring_section(context: &SectionContext) -> bool {
let section_name_suffix = context
.line
.trim()
.strip_prefix(&context.section_name)
.strip_prefix(context.section_name)
.unwrap()
.trim();
let this_looks_like_a_section_name =

View File

@@ -4,7 +4,7 @@ use regex::Regex;
static ALLOWLIST_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
r"(?i)pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|TODO|FIXME|XXX"
r"^(?i)(?:pylint|pyright|noqa|nosec|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|TODO|FIXME|XXX)"
).unwrap()
});
static BRACKET_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^[()\[\]{}\s]+$").unwrap());

View File

@@ -19,24 +19,14 @@ pub fn print_call(checker: &mut Checker, expr: &Expr, func: &Expr) {
};
if checker.patch(check.kind.code()) {
let context = checker.binding_context();
if matches!(
checker.parents[context.defined_by].node,
StmtKind::Expr { .. }
) {
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,
) {
let defined_by = checker.current_parent();
let defined_in = checker.current_grandparent();
if matches!(defined_by.0.node, StmtKind::Expr { .. }) {
let deleted: Vec<&Stmt> = checker.deletions.iter().map(|node| node.0).collect();
match helpers::remove_stmt(defined_by.0, defined_in.map(|node| node.0), &deleted) {
Ok(fix) => {
if fix.content.is_empty() || fix.content == "pass" {
checker.deletions.insert(context.defined_by);
checker.deletions.insert(defined_by.clone());
}
check.amend(fix);
}

View File

@@ -18,6 +18,8 @@ pub struct Stack<'a> {
#[derive(Default)]
pub struct ReturnVisitor<'a> {
pub stack: Stack<'a>,
// If we're in an f-string, the location of the defining expression.
in_f_string: Option<Location>,
}
impl<'a> ReturnVisitor<'a> {
@@ -34,7 +36,7 @@ impl<'a> ReturnVisitor<'a> {
.assigns
.entry(id)
.or_insert_with(Vec::new)
.push(expr.location);
.push(self.in_f_string.unwrap_or(expr.location));
return;
}
_ => {}
@@ -70,7 +72,7 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
.refs
.entry(id)
.or_insert_with(Vec::new)
.push(value.location);
.push(self.in_f_string.unwrap_or(value.location));
}
visitor::walk_expr(self, value);
@@ -111,7 +113,13 @@ impl<'a> Visitor<'a> for ReturnVisitor<'a> {
.refs
.entry(id)
.or_insert_with(Vec::new)
.push(expr.location);
.push(self.in_f_string.unwrap_or(expr.location));
}
ExprKind::JoinedStr { .. } => {
let prev_in_f_string = self.in_f_string;
self.in_f_string = Some(expr.location);
visitor::walk_expr(self, expr);
self.in_f_string = prev_in_f_string;
}
_ => visitor::walk_expr(self, expr),
}

View File

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

View File

@@ -17,12 +17,13 @@ use crate::{visibility, Check};
fn function(
argumentable: &Argumentable,
args: &Arguments,
bindings: &FxHashMap<&str, Binding>,
values: &FxHashMap<&str, usize>,
bindings: &[Binding],
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for arg_name in collect_arg_names(args) {
if let Some(binding) = bindings.get(arg_name) {
if let Some(binding) = values.get(arg_name).map(|index| &bindings[*index]) {
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Argument)
&& !dummy_variable_rgx.is_match(arg_name)
@@ -41,7 +42,8 @@ fn function(
fn method(
argumentable: &Argumentable,
args: &Arguments,
bindings: &FxHashMap<&str, Binding>,
values: &FxHashMap<&str, usize>,
bindings: &[Binding],
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
@@ -54,7 +56,10 @@ fn method(
.chain(iter::once::<Option<&Arg>>(args.vararg.as_deref()).flatten())
.chain(iter::once::<Option<&Arg>>(args.kwarg.as_deref()).flatten())
{
if let Some(binding) = bindings.get(&arg.node.arg.as_str()) {
if let Some(binding) = values
.get(&arg.node.arg.as_str())
.map(|index| &bindings[*index])
{
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Argument)
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
@@ -70,7 +75,12 @@ fn method(
}
/// ARG001, ARG002, ARG003, ARG004, ARG005
pub fn unused_arguments(checker: &Checker, parent: &Scope, scope: &Scope) -> Vec<Check> {
pub fn unused_arguments(
checker: &Checker,
parent: &Scope,
scope: &Scope,
bindings: &[Binding],
) -> Vec<Check> {
match &scope.kind {
ScopeKind::Function(FunctionDef {
name,
@@ -98,6 +108,7 @@ pub fn unused_arguments(checker: &Checker, parent: &Scope, scope: &Scope) -> Vec
&Argumentable::Function,
args,
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
)
} else {
@@ -117,6 +128,7 @@ pub fn unused_arguments(checker: &Checker, parent: &Scope, scope: &Scope) -> Vec
&Argumentable::Method,
args,
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
)
} else {
@@ -136,6 +148,7 @@ pub fn unused_arguments(checker: &Checker, parent: &Scope, scope: &Scope) -> Vec
&Argumentable::ClassMethod,
args,
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
)
} else {
@@ -155,6 +168,7 @@ pub fn unused_arguments(checker: &Checker, parent: &Scope, scope: &Scope) -> Vec
&Argumentable::StaticMethod,
args,
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
)
} else {
@@ -173,6 +187,7 @@ pub fn unused_arguments(checker: &Checker, parent: &Scope, scope: &Scope) -> Vec
&Argumentable::Lambda,
args,
&scope.values,
bindings,
&checker.settings.dummy_variable_rgx,
)
} else {

View File

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

View File

@@ -19,14 +19,12 @@ fn extract_range(body: &[&Stmt]) -> Range {
}
}
fn extract_indentation(body: &[&Stmt], locator: &SourceCodeLocator) -> String {
fn extract_indentation_range(body: &[&Stmt]) -> Range {
let location = body.first().unwrap().location;
let range = Range {
Range {
location: Location::new(location.row(), 0),
end_location: location,
};
let existing = locator.slice_source_code_range(&range);
leading_space(&existing)
}
}
/// I001
@@ -36,8 +34,10 @@ pub fn check_imports(
settings: &Settings,
autofix: bool,
) -> Option<Check> {
let indentation = locator.slice_source_code_range(&extract_indentation_range(&block.imports));
let indentation = leading_space(&indentation);
let range = extract_range(&block.imports);
let indentation = extract_indentation(&block.imports, locator);
// Extract comments. Take care to grab any inline comments from the last line.
let comments = comments::collect_comments(
@@ -77,7 +77,7 @@ pub fn check_imports(
if has_leading_content {
content.push('\n');
}
content.push_str(&indent(&expected, &indentation));
content.push_str(&indent(&expected, indentation));
check.amend(Fix::replacement(
content,
// Preserve leading prefix (but put the imports on a new line).
@@ -104,7 +104,7 @@ pub fn check_imports(
let mut check = Check::new(CheckKind::UnsortedImports, range);
if autofix && settings.fixable.contains(check.kind.code()) {
check.amend(Fix::replacement(
indent(&expected, &indentation),
indent(&expected, indentation),
range.location,
range.end_location,
));

View File

@@ -32,11 +32,16 @@ pub struct Options {
test_id as test_id
)
```
Note that this setting is only effective when combined with `combine-as-imports = true`.
When `combine-as-imports` isn't enabled, every aliased `import from` will be given its
own line, in which case, wrapping is not necessary.
"#,
default = r#"false"#,
value_type = "bool",
example = r#"
force-wrap-aliases = true
combine-as-imports = true
"#
)]
pub force_wrap_aliases: Option<bool>,

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::checks::{Check, CheckCode, REDIRECTS};
use crate::checks::{Check, CheckCode, CODE_REDIRECTS};
static NO_QA_LINE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(
@@ -70,7 +70,7 @@ pub fn extract_noqa_directive(line: &str) -> Directive {
pub fn includes(needle: &CheckCode, haystack: &[&str]) -> bool {
let needle: &str = needle.as_ref();
haystack.iter().any(|candidate| {
if let Some(candidate) = REDIRECTS.get(candidate) {
if let Some(candidate) = CODE_REDIRECTS.get(candidate) {
needle == candidate.as_ref()
} else {
&needle == candidate

View File

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

View File

@@ -298,11 +298,11 @@ pub fn do_not_assign_lambda(checker: &mut Checker, target: &Expr, value: &Expr,
{
match function(id, args, body) {
Ok(content) => {
let indentation =
&leading_space(&checker.locator.slice_source_code_range(&Range {
location: Location::new(stmt.location.row(), 0),
end_location: Location::new(stmt.location.row() + 1, 0),
}));
let first_line = checker.locator.slice_source_code_range(&Range {
location: Location::new(stmt.location.row(), 0),
end_location: Location::new(stmt.location.row() + 1, 0),
});
let indentation = &leading_space(&first_line);
let mut indented = String::new();
for (idx, line) in content.lines().enumerate() {
if idx == 0 {

View File

@@ -1,17 +1,23 @@
use rustpython_ast::Expr;
use crate::ast::types::Range;
use crate::docstrings::constants;
use crate::SourceCodeLocator;
/// Strip the leading and trailing quotes from a docstring.
pub fn raw_contents(contents: &str) -> &str {
for pattern in constants::TRIPLE_QUOTE_PREFIXES {
if contents.starts_with(pattern) {
return &contents[pattern.len()..contents.len() - 3];
}
}
for pattern in constants::SINGLE_QUOTE_PREFIXES {
if contents.starts_with(pattern) {
return &contents[pattern.len()..contents.len() - 1];
}
}
unreachable!("Expected docstring to start with a valid triple- or single-quote prefix")
}
/// Return the leading quote string for a docstring (e.g., `"""`).
pub fn leading_quote<'a>(docstring: &Expr, locator: &'a SourceCodeLocator) -> Option<&'a str> {
if let Some(first_line) = locator
.slice_source_code_range(&Range::from_located(docstring))
.lines()
.next()
.map(str::to_lowercase)
{
pub fn leading_quote(content: &str) -> Option<&str> {
if let Some(first_line) = content.lines().next() {
for pattern in constants::TRIPLE_QUOTE_PREFIXES
.iter()
.chain(constants::SINGLE_QUOTE_PREFIXES)

View File

@@ -1,4 +1,4 @@
mod helpers;
pub mod helpers;
pub mod plugins;
#[cfg(test)]
@@ -37,6 +37,7 @@ mod tests {
#[test_case(CheckCode::D214, Path::new("sections.py"); "D214")]
#[test_case(CheckCode::D215, Path::new("sections.py"); "D215")]
#[test_case(CheckCode::D300, Path::new("D.py"); "D300")]
#[test_case(CheckCode::D301, Path::new("D.py"); "D301")]
#[test_case(CheckCode::D400, Path::new("D.py"); "D400_0")]
#[test_case(CheckCode::D400, Path::new("D400.py"); "D400_1")]
#[test_case(CheckCode::D402, Path::new("D.py"); "D402")]

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
---
source: src/pydocstyle/mod.rs
expression: checks
---
- kind: UsesRPrefixForBackslashedContent
location:
row: 328
column: 4
end_location:
row: 328
column: 16
fix: ~
- kind: UsesRPrefixForBackslashedContent
location:
row: 333
column: 4
end_location:
row: 333
column: 20
fix: ~
- kind: UsesRPrefixForBackslashedContent
location:
row: 338
column: 4
end_location:
row: 338
column: 21
fix: ~

View File

@@ -7,7 +7,7 @@ use rustpython_parser::ast::{
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
};
use crate::ast::types::{BindingKind, Range, Scope, ScopeKind};
use crate::ast::types::{Binding, BindingKind, Range, Scope, ScopeKind};
use crate::checks::{Check, CheckKind};
use crate::pyflakes::cformat::CFormatSummary;
use crate::pyflakes::format::FormatSummary;
@@ -366,12 +366,12 @@ pub fn if_tuple(test: &Expr, location: Range) -> Option<Check> {
}
/// F821
pub fn undefined_local(scopes: &[&Scope], name: &str) -> Option<Check> {
pub fn undefined_local(name: &str, scopes: &[&Scope], bindings: &[Binding]) -> Option<Check> {
let current = &scopes.last().expect("No current scope found");
if matches!(current.kind, ScopeKind::Function(_)) && !current.values.contains_key(name) {
for scope in scopes.iter().rev().skip(1) {
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
if let Some(binding) = scope.values.get(name) {
if let Some(binding) = scope.values.get(name).map(|index| &bindings[*index]) {
if let Some((scope_id, location)) = binding.used {
if scope_id == current.id {
return Some(Check::new(
@@ -388,23 +388,31 @@ pub fn undefined_local(scopes: &[&Scope], name: &str) -> Option<Check> {
}
/// F841
pub fn unused_variables(scope: &Scope, dummy_variable_rgx: &Regex) -> Vec<Check> {
pub fn unused_variable(
scope: &Scope,
bindings: &[Binding],
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
if scope.uses_locals && matches!(scope.kind, ScopeKind::Function(..)) {
return checks;
}
for (&name, binding) in &scope.values {
for (name, binding) in scope
.values
.iter()
.map(|(name, index)| (name, &bindings[*index]))
{
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Assignment)
&& !dummy_variable_rgx.is_match(name)
&& name != "__tracebackhide__"
&& name != "__traceback_info__"
&& name != "__traceback_supplement__"
&& name != &"__tracebackhide__"
&& name != &"__traceback_info__"
&& name != &"__traceback_supplement__"
{
checks.push(Check::new(
CheckKind::UnusedVariable(name.to_string()),
CheckKind::UnusedVariable((*name).to_string()),
binding.range,
));
}
@@ -413,6 +421,31 @@ pub fn unused_variables(scope: &Scope, dummy_variable_rgx: &Regex) -> Vec<Check>
checks
}
/// F842
pub fn unused_annotation(
scope: &Scope,
bindings: &[Binding],
dummy_variable_rgx: &Regex,
) -> Vec<Check> {
let mut checks: Vec<Check> = vec![];
for (name, binding) in scope
.values
.iter()
.map(|(name, index)| (name, &bindings[*index]))
{
if binding.used.is_none()
&& matches!(binding.kind, BindingKind::Annotation)
&& !dummy_variable_rgx.is_match(name)
{
checks.push(Check::new(
CheckKind::UnusedAnnotation((*name).to_string()),
binding.range,
));
}
}
checks
}
/// F707
pub fn default_except_not_last(handlers: &[Excepthandler]) -> Option<Check> {
for (idx, handler) in handlers.iter().enumerate() {
@@ -546,12 +579,13 @@ pub fn starred_expressions(
}
/// F701
pub fn break_outside_loop(stmt: &Stmt, parents: &[&Stmt], parent_stack: &[usize]) -> Option<Check> {
pub fn break_outside_loop<'a>(
stmt: &'a Stmt,
parents: &mut impl Iterator<Item = &'a Stmt>,
) -> Option<Check> {
let mut allowed: bool = false;
let mut parent = stmt;
for index in parent_stack.iter().rev() {
let child = parent;
parent = parents[*index];
let mut child = stmt;
for parent in parents {
match &parent.node {
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
@@ -561,7 +595,6 @@ pub fn break_outside_loop(stmt: &Stmt, parents: &[&Stmt], parent_stack: &[usize]
break;
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
@@ -569,6 +602,7 @@ pub fn break_outside_loop(stmt: &Stmt, parents: &[&Stmt], parent_stack: &[usize]
}
_ => {}
}
child = parent;
}
if allowed {
@@ -582,16 +616,13 @@ pub fn break_outside_loop(stmt: &Stmt, parents: &[&Stmt], parent_stack: &[usize]
}
/// F702
pub fn continue_outside_loop(
stmt: &Stmt,
parents: &[&Stmt],
parent_stack: &[usize],
pub fn continue_outside_loop<'a>(
stmt: &'a Stmt,
parents: &mut impl Iterator<Item = &'a Stmt>,
) -> Option<Check> {
let mut allowed: bool = false;
let mut parent = stmt;
for index in parent_stack.iter().rev() {
let child = parent;
parent = parents[*index];
let mut child = stmt;
for parent in parents {
match &parent.node {
StmtKind::For { orelse, .. }
| StmtKind::AsyncFor { orelse, .. }
@@ -601,7 +632,6 @@ pub fn continue_outside_loop(
break;
}
}
StmtKind::FunctionDef { .. }
| StmtKind::AsyncFunctionDef { .. }
| StmtKind::ClassDef { .. } => {
@@ -609,6 +639,7 @@ pub fn continue_outside_loop(
}
_ => {}
}
child = parent;
}
if allowed {

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
use rustpython_ast::{Expr, ExprKind};
use crate::ast::types::{Binding, BindingKind, Range};
use crate::ast::types::Range;
use crate::check_ast::Checker;
use crate::checks::{Check, CheckKind};
@@ -12,12 +12,7 @@ pub fn invalid_print_syntax(checker: &mut Checker, left: &Expr) {
if id != "print" {
return;
}
let scope = checker.current_scope();
let Some(Binding {
kind: BindingKind::Builtin,
..
}) = scope.values.get("print") else
{
if !checker.is_builtin("print") {
return;
};
checker.add_check(Check::new(

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- bar
- 6
location:
row: 10
column: 0
end_location:
row: 12
column: 0
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- FU
- 1
location:
row: 1
column: 17
end_location:
row: 1
column: 26
fix: ~

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- mixer
- 2
location:
row: 6
column: 19
end_location:
row: 6
column: 24
fix: ~

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- fu
- 1
location:
row: 4
column: 0
end_location:
row: 6
column: 0
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- fu
- 3
location:
row: 8
column: 8
end_location:
row: 10
column: 0
fix: ~

View File

@@ -0,0 +1,27 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- fu
- 2
location:
row: 6
column: 11
end_location:
row: 6
column: 13
fix: ~
- kind:
RedefinedWhileUnused:
- fu
- 6
location:
row: 9
column: 8
end_location:
row: 11
column: 0
fix: ~

View File

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

View File

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

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- FU
- 1
location:
row: 1
column: 26
end_location:
row: 1
column: 35
fix: ~

View File

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

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- fu
- 1
location:
row: 1
column: 11
end_location:
row: 1
column: 13
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- fu
- 1
location:
row: 1
column: 11
end_location:
row: 1
column: 13
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- fu
- 1
location:
row: 1
column: 12
end_location:
row: 1
column: 14
fix: ~

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- os
- 5
location:
row: 6
column: 11
end_location:
row: 6
column: 13
fix: ~

View File

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

View File

@@ -0,0 +1,16 @@
---
source: src/pyflakes/mod.rs
expression: checks
---
- kind:
RedefinedWhileUnused:
- os
- 4
location:
row: 5
column: 11
end_location:
row: 5
column: 13
fix: ~

View File

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

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