Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
257bd7f1d7 | ||
|
|
5728dceef0 | ||
|
|
6739602806 | ||
|
|
305326f7d7 | ||
|
|
69866f5461 | ||
|
|
41ca29c4f4 | ||
|
|
b35a804f9d | ||
|
|
e594ed6528 | ||
|
|
197645d90d | ||
|
|
26d3ff5a3a | ||
|
|
0dacf61153 | ||
|
|
a6251360b7 | ||
|
|
2965e2561d | ||
|
|
a19050b8a4 | ||
|
|
dfd6225d85 | ||
|
|
a0a6327fae | ||
|
|
db815a565f | ||
|
|
3bacdafd1c | ||
|
|
6403e3630d | ||
|
|
229eab6f42 | ||
|
|
e33582fb0e | ||
|
|
aaeab0ecf1 | ||
|
|
f9a16d9c44 | ||
|
|
2aa884eb9b | ||
|
|
84fa64d98c | ||
|
|
c1b1ac069e |
5
.github/workflows/ruff.yaml
vendored
5
.github/workflows/ruff.yaml
vendored
@@ -1,9 +1,8 @@
|
||||
name: "[ruff] Release"
|
||||
|
||||
on:
|
||||
create:
|
||||
tags:
|
||||
- v*
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.170
|
||||
rev: v0.0.173
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -724,7 +724,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.170-dev.0"
|
||||
version = "0.0.173-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1821,7 +1821,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.170"
|
||||
version = "0.0.173"
|
||||
dependencies = [
|
||||
"annotate-snippets 0.9.1",
|
||||
"anyhow",
|
||||
@@ -1874,7 +1874,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.170"
|
||||
version = "0.0.173"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.29",
|
||||
@@ -1892,7 +1892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.170"
|
||||
version = "0.0.173"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.170"
|
||||
version = "0.0.173"
|
||||
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.170", path = "ruff_macros" }
|
||||
ruff_macros = { version = "0.0.173", 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" }
|
||||
|
||||
12
README.md
12
README.md
@@ -147,7 +147,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.170
|
||||
rev: v0.0.173
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -438,11 +438,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)
|
||||
@@ -515,6 +517,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 | |
|
||||
@@ -948,7 +951,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:
|
||||
@@ -1980,6 +1983,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`
|
||||
@@ -1989,6 +1996,7 @@ from .utils import (
|
||||
```toml
|
||||
[tool.ruff.isort]
|
||||
force-wrap-aliases = true
|
||||
combine-as-imports = true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
4
flake8_to_ruff/Cargo.lock
generated
4
flake8_to_ruff/Cargo.lock
generated
@@ -771,7 +771,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8_to_ruff"
|
||||
version = "0.0.170"
|
||||
version = "0.0.173"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.170"
|
||||
version = "0.0.173"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.170-dev.0"
|
||||
version = "0.0.173-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
@@ -512,6 +512,7 @@ mod tests {
|
||||
CheckCodePrefix::D214,
|
||||
CheckCodePrefix::D215,
|
||||
CheckCodePrefix::D300,
|
||||
CheckCodePrefix::D301,
|
||||
CheckCodePrefix::D400,
|
||||
CheckCodePrefix::D403,
|
||||
CheckCodePrefix::D404,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
4
resources/test/fixtures/eradicate/ERA001.py
vendored
4
resources/test/fixtures/eradicate/ERA001.py
vendored
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
11
resources/test/fixtures/pyflakes/F811_0.py
vendored
Normal file
11
resources/test/fixtures/pyflakes/F811_0.py
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
def foo(x):
|
||||
return x
|
||||
|
||||
|
||||
@foo
|
||||
def bar():
|
||||
pass
|
||||
|
||||
|
||||
def bar():
|
||||
pass
|
||||
1
resources/test/fixtures/pyflakes/F811_1.py
vendored
Normal file
1
resources/test/fixtures/pyflakes/F811_1.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fu as FU, bar as FU
|
||||
8
resources/test/fixtures/pyflakes/F811_10.py
vendored
Normal file
8
resources/test/fixtures/pyflakes/F811_10.py
vendored
Normal 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
|
||||
9
resources/test/fixtures/pyflakes/F811_11.py
vendored
Normal file
9
resources/test/fixtures/pyflakes/F811_11.py
vendored
Normal 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)
|
||||
7
resources/test/fixtures/pyflakes/F811_12.py
vendored
Normal file
7
resources/test/fixtures/pyflakes/F811_12.py
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
try:
|
||||
from aa import mixer
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
from bb import mixer
|
||||
mixer(123)
|
||||
8
resources/test/fixtures/pyflakes/F811_13.py
vendored
Normal file
8
resources/test/fixtures/pyflakes/F811_13.py
vendored
Normal 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)
|
||||
10
resources/test/fixtures/pyflakes/F811_14.py
vendored
Normal file
10
resources/test/fixtures/pyflakes/F811_14.py
vendored
Normal 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)
|
||||
5
resources/test/fixtures/pyflakes/F811_15.py
vendored
Normal file
5
resources/test/fixtures/pyflakes/F811_15.py
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import fu
|
||||
|
||||
|
||||
def fu():
|
||||
pass
|
||||
9
resources/test/fixtures/pyflakes/F811_16.py
vendored
Normal file
9
resources/test/fixtures/pyflakes/F811_16.py
vendored
Normal 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
|
||||
10
resources/test/fixtures/pyflakes/F811_17.py
vendored
Normal file
10
resources/test/fixtures/pyflakes/F811_17.py
vendored
Normal 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
|
||||
14
resources/test/fixtures/pyflakes/F811_18.py
vendored
Normal file
14
resources/test/fixtures/pyflakes/F811_18.py
vendored
Normal 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()
|
||||
7
resources/test/fixtures/pyflakes/F811_19.py
vendored
Normal file
7
resources/test/fixtures/pyflakes/F811_19.py
vendored
Normal 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
|
||||
1
resources/test/fixtures/pyflakes/F811_2.py
vendored
Normal file
1
resources/test/fixtures/pyflakes/F811_2.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
from moo import fu as FU, bar as FU
|
||||
14
resources/test/fixtures/pyflakes/F811_20.py
vendored
Normal file
14
resources/test/fixtures/pyflakes/F811_20.py
vendored
Normal 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)
|
||||
1
resources/test/fixtures/pyflakes/F811_3.py
vendored
Normal file
1
resources/test/fixtures/pyflakes/F811_3.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fu; fu = 3
|
||||
1
resources/test/fixtures/pyflakes/F811_4.py
vendored
Normal file
1
resources/test/fixtures/pyflakes/F811_4.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fu; fu, bar = 3
|
||||
1
resources/test/fixtures/pyflakes/F811_5.py
vendored
Normal file
1
resources/test/fixtures/pyflakes/F811_5.py
vendored
Normal file
@@ -0,0 +1 @@
|
||||
import fu; [fu, bar] = 3
|
||||
7
resources/test/fixtures/pyflakes/F811_6.py
vendored
Normal file
7
resources/test/fixtures/pyflakes/F811_6.py
vendored
Normal 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
|
||||
8
resources/test/fixtures/pyflakes/F811_7.py
vendored
Normal file
8
resources/test/fixtures/pyflakes/F811_7.py
vendored
Normal 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
|
||||
8
resources/test/fixtures/pyflakes/F811_8.py
vendored
Normal file
8
resources/test/fixtures/pyflakes/F811_8.py
vendored
Normal 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
|
||||
7
resources/test/fixtures/pyflakes/F811_9.py
vendored
Normal file
7
resources/test/fixtures/pyflakes/F811_9.py
vendored
Normal 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
|
||||
13
resources/test/fixtures/pyflakes/F842.py
vendored
Normal file
13
resources/test/fixtures/pyflakes/F842.py
vendored
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.170"
|
||||
version = "0.0.173"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_macros"
|
||||
version = "0.0.170"
|
||||
version = "0.0.173"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
|
||||
105
src/ast/branch_detection.rs
Normal file
105
src/ast/branch_detection.rs
Normal 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
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod branch_detection;
|
||||
pub mod cast;
|
||||
pub mod function_type;
|
||||
pub mod helpers;
|
||||
|
||||
@@ -3,10 +3,10 @@ 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};
|
||||
|
||||
/// 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 +23,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);
|
||||
}
|
||||
}
|
||||
|
||||
108
src/ast/types.rs
108
src/ast/types.rs
@@ -73,7 +73,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 +88,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> {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
894
src/check_ast.rs
894
src/check_ast.rs
File diff suppressed because it is too large
Load Diff
@@ -87,11 +87,13 @@ pub enum CheckCode {
|
||||
F706,
|
||||
F707,
|
||||
F722,
|
||||
F811,
|
||||
F821,
|
||||
F822,
|
||||
F823,
|
||||
F831,
|
||||
F841,
|
||||
F842,
|
||||
F901,
|
||||
// pylint
|
||||
PLC0414,
|
||||
@@ -242,6 +244,7 @@ pub enum CheckCode {
|
||||
D214,
|
||||
D215,
|
||||
D300,
|
||||
D301,
|
||||
D400,
|
||||
D402,
|
||||
D403,
|
||||
@@ -616,6 +619,7 @@ pub enum CheckKind {
|
||||
PercentFormatStarRequiresSequence,
|
||||
PercentFormatUnsupportedFormatCharacter(char),
|
||||
RaiseNotImplemented,
|
||||
RedefinedWhileUnused(String, usize),
|
||||
ReturnOutsideFunction,
|
||||
StringDotFormatExtraNamedArguments(Vec<String>),
|
||||
StringDotFormatExtraPositionalArguments(Vec<String>),
|
||||
@@ -625,6 +629,7 @@ pub enum CheckKind {
|
||||
TwoStarredExpressions,
|
||||
UndefinedExport(String),
|
||||
UndefinedLocal(String),
|
||||
UnusedAnnotation(String),
|
||||
UndefinedName(String),
|
||||
UnusedImport(String, bool),
|
||||
UnusedVariable(String),
|
||||
@@ -794,6 +799,7 @@ pub enum CheckKind {
|
||||
SectionUnderlineMatchesSectionLength(String),
|
||||
SectionUnderlineNotOverIndented(String),
|
||||
SkipDocstring,
|
||||
UsesRPrefixForBackslashedContent,
|
||||
UsesTripleQuotes,
|
||||
// pep8-naming
|
||||
InvalidClassName(String),
|
||||
@@ -932,11 +938,13 @@ 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,
|
||||
@@ -1109,6 +1117,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 +1298,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,11 +1369,13 @@ 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,
|
||||
@@ -1508,9 +1520,11 @@ 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,
|
||||
@@ -1680,6 +1694,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 +1861,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 +1912,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!(
|
||||
@@ -1923,8 +1944,8 @@ impl CheckKind {
|
||||
let types = types.join(", ");
|
||||
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::UnnecessaryDirectLambdaCall => "Lambda expression called directly. Execute \
|
||||
the expression inline instead."
|
||||
@@ -2332,6 +2353,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()
|
||||
|
||||
@@ -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,
|
||||
@@ -759,6 +763,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::D214,
|
||||
CheckCode::D215,
|
||||
CheckCode::D300,
|
||||
CheckCode::D301,
|
||||
CheckCode::D400,
|
||||
CheckCode::D402,
|
||||
CheckCode::D403,
|
||||
@@ -861,9 +866,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,
|
||||
@@ -1026,11 +1032,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![
|
||||
@@ -1158,20 +1166,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],
|
||||
@@ -1929,6 +1942,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,
|
||||
@@ -2033,6 +2047,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,
|
||||
@@ -2041,6 +2057,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,
|
||||
|
||||
@@ -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'", "\"", "'",
|
||||
];
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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: ~
|
||||
|
||||
@@ -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 {
|
||||
|
||||
1
src/pyflakes/foo.rs
Normal file
1
src/pyflakes/foo.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
3732
src/pyflakes/mod.rs
3732
src/pyflakes/mod.rs
File diff suppressed because it is too large
Load Diff
@@ -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(
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -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: ~
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
[]
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
source: src/pyflakes/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedAnnotation: name
|
||||
location:
|
||||
row: 2
|
||||
column: 4
|
||||
end_location:
|
||||
row: 2
|
||||
column: 8
|
||||
fix: ~
|
||||
- kind:
|
||||
UnusedAnnotation: age
|
||||
location:
|
||||
row: 3
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
column: 7
|
||||
fix: ~
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::checks::{Check, CheckKind};
|
||||
/// sys import *`).
|
||||
fn is_module_star_imported(checker: &Checker, module: &str) -> bool {
|
||||
checker.current_scopes().any(|scope| {
|
||||
scope.values.values().any(|binding| {
|
||||
if let BindingKind::StarImportation(_, name) = &binding.kind {
|
||||
scope.values.values().any(|index| {
|
||||
if let BindingKind::StarImportation(_, name) = &checker.bindings[*index].kind {
|
||||
name.as_ref().map(|name| name == module).unwrap_or_default()
|
||||
} else {
|
||||
false
|
||||
@@ -26,17 +26,17 @@ fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -
|
||||
scope
|
||||
.values
|
||||
.values()
|
||||
.find_map(|binding| match &binding.kind {
|
||||
.find_map(|index| match &checker.bindings[*index].kind {
|
||||
// e.g. module=sys object=exit
|
||||
// `import sys` -> `sys.exit`
|
||||
// `import sys as sys2` -> `sys2.exit`
|
||||
BindingKind::Importation(name, full_name, _) if full_name == module => {
|
||||
BindingKind::Importation(name, full_name) if full_name == module => {
|
||||
Some(format!("{name}.{member}"))
|
||||
}
|
||||
// e.g. module=os.path object=join
|
||||
// `from os.path import join` -> `join`
|
||||
// `from os.path import join as join2` -> `join2`
|
||||
BindingKind::FromImportation(name, full_name, _)
|
||||
BindingKind::FromImportation(name, full_name)
|
||||
if full_name == &format!("{module}.{member}") =>
|
||||
{
|
||||
Some(name.to_string())
|
||||
@@ -50,7 +50,7 @@ fn get_member_import_name_alias(checker: &Checker, module: &str, member: &str) -
|
||||
}
|
||||
// e.g. module=os.path object=join
|
||||
// `import os.path ` -> `os.path.join`
|
||||
BindingKind::SubmoduleImportation(_, full_name, _) if full_name == module => {
|
||||
BindingKind::SubmoduleImportation(_, full_name) if full_name == module => {
|
||||
Some(format!("{full_name}.{member}"))
|
||||
}
|
||||
// Non-imports.
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::Check;
|
||||
|
||||
fn loop_exits_early(body: &[Stmt]) -> bool {
|
||||
body.iter().any(|stmt| match &stmt.node {
|
||||
StmtKind::If { body, .. } => loop_exits_early(body),
|
||||
StmtKind::If { body, orelse, .. } => loop_exits_early(body) || loop_exits_early(orelse),
|
||||
StmtKind::Try {
|
||||
body,
|
||||
handlers,
|
||||
@@ -16,11 +16,11 @@ fn loop_exits_early(body: &[Stmt]) -> bool {
|
||||
..
|
||||
} => {
|
||||
loop_exits_early(body)
|
||||
|| loop_exits_early(orelse)
|
||||
|| loop_exits_early(finalbody)
|
||||
|| handlers.iter().any(|handler| match &handler.node {
|
||||
ExcepthandlerKind::ExceptHandler { body, .. } => loop_exits_early(body),
|
||||
})
|
||||
|| loop_exits_early(orelse)
|
||||
|| loop_exits_early(finalbody)
|
||||
}
|
||||
StmtKind::For { orelse, .. }
|
||||
| StmtKind::AsyncFor { orelse, .. }
|
||||
|
||||
@@ -101,7 +101,12 @@ pub fn useless_metaclass_type(targets: &[Expr], value: &Expr, location: Range) -
|
||||
}
|
||||
|
||||
/// UP004
|
||||
pub fn useless_object_inheritance(name: &str, bases: &[Expr], scope: &Scope) -> Option<Check> {
|
||||
pub fn useless_object_inheritance(
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
scope: &Scope,
|
||||
bindings: &[Binding],
|
||||
) -> Option<Check> {
|
||||
for expr in bases {
|
||||
let ExprKind::Name { id, .. } = &expr.node else {
|
||||
continue;
|
||||
@@ -110,7 +115,10 @@ pub fn useless_object_inheritance(name: &str, bases: &[Expr], scope: &Scope) ->
|
||||
continue;
|
||||
}
|
||||
if !matches!(
|
||||
scope.values.get(&id.as_str()),
|
||||
scope
|
||||
.values
|
||||
.get(&id.as_str())
|
||||
.map(|index| &bindings[*index]),
|
||||
None | Some(Binding {
|
||||
kind: BindingKind::Builtin,
|
||||
..
|
||||
|
||||
@@ -13,11 +13,7 @@ pub fn super_call_with_parameters(checker: &mut Checker, expr: &Expr, func: &Exp
|
||||
return;
|
||||
}
|
||||
let scope = checker.current_scope();
|
||||
let parents: Vec<&Stmt> = checker
|
||||
.parent_stack
|
||||
.iter()
|
||||
.map(|index| checker.parents[*index])
|
||||
.collect();
|
||||
let parents: Vec<&Stmt> = checker.parents.iter().map(|node| node.0).collect();
|
||||
let Some(mut check) = checks::super_args(scope, &parents, expr, func, args) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -62,23 +62,21 @@ pub fn unnecessary_future_import(checker: &mut Checker, stmt: &Stmt, names: &[Lo
|
||||
),
|
||||
Range::from_located(stmt),
|
||||
);
|
||||
|
||||
if checker.patch(check.kind.code()) {
|
||||
let context = checker.binding_context();
|
||||
let deleted: Vec<&Stmt> = checker
|
||||
.deletions
|
||||
.iter()
|
||||
.map(|index| checker.parents[*index])
|
||||
.collect();
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(|node| node.0).collect();
|
||||
let defined_by = checker.current_parent();
|
||||
let defined_in = checker.current_grandparent();
|
||||
match fixes::remove_unnecessary_future_import(
|
||||
checker.locator,
|
||||
&removable_index,
|
||||
checker.parents[context.defined_by],
|
||||
context.defined_in.map(|index| checker.parents[index]),
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -13,21 +13,13 @@ pub fn useless_metaclass_type(checker: &mut Checker, stmt: &Stmt, value: &Expr,
|
||||
return;
|
||||
};
|
||||
if checker.patch(check.kind.code()) {
|
||||
let context = checker.binding_context();
|
||||
let deleted: Vec<&Stmt> = checker
|
||||
.deletions
|
||||
.iter()
|
||||
.map(|index| checker.parents[*index])
|
||||
.collect();
|
||||
|
||||
match helpers::remove_stmt(
|
||||
checker.parents[context.defined_by],
|
||||
context.defined_in.map(|index| checker.parents[index]),
|
||||
&deleted,
|
||||
) {
|
||||
let deleted: Vec<&Stmt> = checker.deletions.iter().map(|node| node.0).collect();
|
||||
let defined_by = checker.current_parent();
|
||||
let defined_in = checker.current_grandparent();
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,7 @@ pub fn useless_object_inheritance(
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
) {
|
||||
let scope = checker.current_scope();
|
||||
let Some(mut check) = checks::useless_object_inheritance(name, bases, scope) else {
|
||||
let Some(mut check) = checks::useless_object_inheritance(name, bases, checker.current_scope(), &checker.bindings) else {
|
||||
return;
|
||||
};
|
||||
if checker.patch(check.kind.code()) {
|
||||
|
||||
Reference in New Issue
Block a user