Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9d136de55a | ||
|
|
1821c07367 | ||
|
|
1fe90ef7f4 | ||
|
|
b5cb9485f6 | ||
|
|
4d798512b1 | ||
|
|
5f9815b103 | ||
|
|
0d3fac1bf9 | ||
|
|
ff0e5f5cb4 | ||
|
|
374d57d822 | ||
|
|
85b2a9920f | ||
|
|
3c22913470 | ||
|
|
ea03a59b72 | ||
|
|
058a5276b0 | ||
|
|
62d4096be3 | ||
|
|
8961da7b89 | ||
|
|
58bcffbe2d | ||
|
|
f67727b13c | ||
|
|
fea029ae35 | ||
|
|
3e3c3c7421 | ||
|
|
9047bf680d | ||
|
|
d170388b7b | ||
|
|
502d3316f9 | ||
|
|
a8159f9893 | ||
|
|
71f727c380 | ||
|
|
ce3c45a361 | ||
|
|
29ae6c159d | ||
|
|
1ae07b4c70 | ||
|
|
08ca8788a7 | ||
|
|
8a97a76038 | ||
|
|
d2d84cf5bf | ||
|
|
450970e0e6 | ||
|
|
34ecc69914 | ||
|
|
a310aed128 | ||
|
|
43cc8bc84e | ||
|
|
84bf36194b | ||
|
|
e4d168bb4f | ||
|
|
439642addf | ||
|
|
f5b1f957e3 | ||
|
|
8f99705795 | ||
|
|
9ec7e6bcd6 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -77,7 +77,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
- run: cargo clippy --all -- -D warnings
|
||||
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
cargo_test:
|
||||
name: "cargo test"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.115
|
||||
rev: v0.0.121
|
||||
hooks:
|
||||
- id: ruff
|
||||
|
||||
|
||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -930,11 +930,12 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||
|
||||
[[package]]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.115-dev.0"
|
||||
version = "0.0.121-dev.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
"configparser",
|
||||
"fnv",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"ruff",
|
||||
@@ -2237,10 +2238,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.115"
|
||||
version = "0.0.121"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"atty",
|
||||
"bincode",
|
||||
"bitflags",
|
||||
"cacache",
|
||||
@@ -2285,7 +2287,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.115"
|
||||
version = "0.0.121"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap 4.0.22",
|
||||
|
||||
@@ -6,7 +6,7 @@ members = [
|
||||
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.0.115"
|
||||
version = "0.0.121"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -14,6 +14,7 @@ name = "ruff"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.66" }
|
||||
atty = { version = "0.2.14" }
|
||||
bincode = { version = "1.3.3" }
|
||||
bitflags = { version = "1.3.2" }
|
||||
chrono = { version = "0.4.21", default-features = false, features = ["clock"] }
|
||||
|
||||
85
README.md
85
README.md
@@ -36,8 +36,8 @@ faster than any individual tool.
|
||||
automatically convert your existing configuration.)
|
||||
|
||||
Ruff is actively developed and used in major open-source projects
|
||||
like [Zulip](https://github.com/zulip/zulip), [pydantic](https://github.com/pydantic/pydantic),
|
||||
and [Saleor](https://github.com/saleor/saleor).
|
||||
like [FastAPI](https://github.com/tiangolo/fastapi), [Zulip](https://github.com/zulip/zulip),
|
||||
[pydantic](https://github.com/pydantic/pydantic), and [Saleor](https://github.com/saleor/saleor).
|
||||
|
||||
Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-much-much-faster).
|
||||
|
||||
@@ -48,18 +48,20 @@ Read the [launch blog post](https://notes.crmarsh.com/python-tooling-could-be-mu
|
||||
3. [Supported Rules](#supported-rules)
|
||||
1. [Pyflakes](#pyflakes)
|
||||
2. [pycodestyle](#pycodestyle)
|
||||
3. [pydocstyle](#pydocstyle)
|
||||
4. [pyupgrade](#pyupgrade)
|
||||
5. [pep8-naming](#pep8-naming)
|
||||
6. [flake8-comprehensions](#flake8-comprehensions)
|
||||
7. [flake8-bugbear](#flake8-bugbear)
|
||||
8. [flake8-builtins](#flake8-builtins)
|
||||
9. [flake8-print](#flake8-print)
|
||||
10. [flake8-quotes](#flake8-quotes)
|
||||
11. [flake8-annotations](#flake8-annotations)
|
||||
12. [flake8-2020](#flake8-2020)
|
||||
13. [Ruff-specific rules](#ruff-specific-rules)
|
||||
14. [Meta rules](#meta-rules)
|
||||
3. [isort](#isort)
|
||||
4. [pydocstyle](#pydocstyle)
|
||||
5. [pyupgrade](#pyupgrade)
|
||||
6. [pep8-naming](#pep8-naming)
|
||||
7. [flake8-bandit](#flake8-bandit)
|
||||
8. [flake8-comprehensions](#flake8-comprehensions)
|
||||
9. [flake8-bugbear](#flake8-bugbear)
|
||||
10. [flake8-builtins](#flake8-builtins)
|
||||
11. [flake8-print](#flake8-print)
|
||||
12. [flake8-quotes](#flake8-quotes)
|
||||
13. [flake8-annotations](#flake8-annotations)
|
||||
14. [flake8-2020](#flake8-2020)
|
||||
15. [Ruff-specific rules](#ruff-specific-rules)
|
||||
16. [Meta rules](#meta-rules)
|
||||
5. [Editor Integrations](#editor-integrations)
|
||||
6. [FAQ](#faq)
|
||||
7. [Development](#development)
|
||||
@@ -99,7 +101,7 @@ Ruff also works with [pre-commit](https://pre-commit.com):
|
||||
```yaml
|
||||
repos:
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: v0.0.115
|
||||
rev: v0.0.116
|
||||
hooks:
|
||||
- id: ruff
|
||||
```
|
||||
@@ -235,6 +237,8 @@ Options:
|
||||
Regular expression matching the name of dummy variables
|
||||
--target-version <TARGET_VERSION>
|
||||
The minimum Python version that should be supported
|
||||
--line-length <LINE_LENGTH>
|
||||
Set the line-length for length-associated checks and automatic formatting
|
||||
--stdin-filename <STDIN_FILENAME>
|
||||
The name of the file when passing it through stdin
|
||||
-h, --help
|
||||
@@ -335,7 +339,7 @@ For more, see [Pyflakes](https://pypi.org/project/pyflakes/2.5.0/) on PyPI.
|
||||
| F702 | ContinueOutsideLoop | `continue` not properly in loop | |
|
||||
| F704 | YieldOutsideFunction | `yield` or `yield from` statement outside of a function | |
|
||||
| F706 | ReturnOutsideFunction | `return` statement outside of a function/method | |
|
||||
| F707 | DefaultExceptNotLast | An `except:` block as not the last exception handler | |
|
||||
| F707 | DefaultExceptNotLast | An `except` block as not the last exception handler | |
|
||||
| F722 | ForwardAnnotationSyntaxError | Syntax error in forward annotation: `...` | |
|
||||
| F821 | UndefinedName | Undefined name `...` | |
|
||||
| F822 | UndefinedExport | Undefined name `...` in `__all__` | |
|
||||
@@ -409,7 +413,7 @@ For more, see [pydocstyle](https://pypi.org/project/pydocstyle/6.1.1/) on PyPI.
|
||||
| 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 | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be 'This' | |
|
||||
| D404 | NoThisPrefix | First word of the docstring should not be "This" | |
|
||||
| D405 | CapitalizeSectionName | Section name should be properly capitalized ("returns") | 🛠 |
|
||||
| D406 | NewLineAfterSectionName | Section name should end with a newline ("Returns") | 🛠 |
|
||||
| D407 | DashedUnderlineAfterSection | Missing dashed underline after section ("Returns") | 🛠 |
|
||||
@@ -440,7 +444,7 @@ For more, see [pyupgrade](https://pypi.org/project/pyupgrade/3.2.0/) on PyPI.
|
||||
| U006 | UsePEP585Annotation | Use `list` instead of `List` for type annotations | 🛠 |
|
||||
| U007 | UsePEP604Annotation | Use `X \| Y` for type annotations | 🛠 |
|
||||
| U008 | SuperCallWithParameters | Use `super()` instead of `super(__class__, self)` | 🛠 |
|
||||
| U009 | PEP3120UnnecessaryCodingComment | utf-8 encoding declaration is unnecessary | 🛠 |
|
||||
| U009 | PEP3120UnnecessaryCodingComment | UTF-8 encoding declaration is unnecessary | 🛠 |
|
||||
| U010 | UnnecessaryFutureImport | Unnecessary `__future__` import `...` for target Python version | 🛠 |
|
||||
| U011 | UnnecessaryLRUCacheParams | Unnecessary parameters to `functools.lru_cache` | 🛠 |
|
||||
| U012 | UnnecessaryEncodeUTF8 | Unnecessary call to `encode` as UTF-8 | 🛠 |
|
||||
@@ -476,9 +480,9 @@ For more, see [flake8-bandit](https://pypi.org/project/flake8-bandit/4.1.1/) on
|
||||
| S101 | AssertUsed | Use of `assert` detected | |
|
||||
| S102 | ExecUsed | Use of `exec` detected | |
|
||||
| S104 | HardcodedBindAllInterfaces | Possible binding to all interfaces | |
|
||||
| S105 | HardcodedPasswordString | Possible hardcoded password: `'...'` | |
|
||||
| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: `'...'` | |
|
||||
| S107 | HardcodedPasswordDefault | Possible hardcoded password: `'...'` | |
|
||||
| S105 | HardcodedPasswordString | Possible hardcoded password: `"..."` | |
|
||||
| S106 | HardcodedPasswordFuncArg | Possible hardcoded password: `"..."` | |
|
||||
| S107 | HardcodedPasswordDefault | Possible hardcoded password: `"..."` | |
|
||||
|
||||
### flake8-comprehensions
|
||||
|
||||
@@ -509,25 +513,31 @@ For more, see [flake8-bugbear](https://pypi.org/project/flake8-bugbear/22.10.27/
|
||||
|
||||
| Code | Name | Message | Fix |
|
||||
| ---- | ---- | ------- | --- |
|
||||
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment. | |
|
||||
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment. | |
|
||||
| B002 | UnaryPrefixIncrement | Python does not support the unary prefix increment | |
|
||||
| B003 | AssignmentToOsEnviron | Assigning to `os.environ` doesn't clear the environment | |
|
||||
| B004 | UnreliableCallableCheck | Using `hasattr(x, '__call__')` to test if x is callable is unreliable. Use `callable(x)` for consistent results. | |
|
||||
| B005 | StripWithMultiCharacters | Using `.strip()` with multi-character strings is misleading the reader. | |
|
||||
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults. | |
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body. | 🛠 |
|
||||
| B008 | FunctionCallArgumentDefault | Do not perform function calls in argument defaults. | |
|
||||
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value, it is not any safer than normal property access. | 🛠 |
|
||||
| B010 | SetAttrWithConstant | Do not call `setattr` with a constant attribute value, it is not any safer than normal property access. | |
|
||||
| B005 | StripWithMultiCharacters | Using `.strip()` with multi-character strings is misleading the reader | |
|
||||
| B006 | MutableArgumentDefault | Do not use mutable data structures for argument defaults | |
|
||||
| B007 | UnusedLoopControlVariable | Loop control variable `i` not used within the loop body | 🛠 |
|
||||
| B008 | FunctionCallArgumentDefault | Do not perform function call in argument defaults | |
|
||||
| B009 | GetAttrWithConstant | Do not call `getattr` with a constant attribute value. It is not any safer than normal property access. | 🛠 |
|
||||
| B010 | SetAttrWithConstant | Do not call `setattr` with a constant attribute value. It is not any safer than normal property access. | |
|
||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError:` instead of `except (ValueError,):`. | |
|
||||
| B012 | JumpStatementInFinally | `return/continue/break` inside finally blocks cause exceptions to be silenced | |
|
||||
| B013 | RedundantTupleInExceptionHandler | A length-one tuple literal is redundant. Write `except ValueError` instead of `except (ValueError,)`. | |
|
||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||
| B015 | UselessComparison | Pointless comparison. This comparison does nothing but waste CPU instructions. Either prepend `assert` or remove it. | |
|
||||
| B016 | CannotRaiseLiteral | Cannot raise a literal. Did you intend to return it or raise an Exception? | |
|
||||
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
|
||||
| B017 | NoAssertRaisesException | `assertRaises(Exception)` should be considered evil | |
|
||||
| B018 | UselessExpression | Found useless expression. Either assign it to a variable or remove it. | |
|
||||
| B019 | CachedInstanceMethod | Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks. | |
|
||||
| B019 | CachedInstanceMethod | Use of `functools.lru_cache` or `functools.cache` on methods can lead to memory leaks | |
|
||||
| B020 | LoopVariableOverridesIterator | Loop control variable `...` overrides iterable it iterates | |
|
||||
| B021 | FStringDocstring | f-string used as docstring. This will be interpreted by python as a joined string rather than a docstring. | |
|
||||
| B022 | UselessContextlibSuppress | No arguments passed to `contextlib.suppress`. No exceptions will be suppressed and therefore this context manager is redundant | |
|
||||
| B024 | AbstractBaseClassWithoutAbstractMethod | `...` is an abstract base class, but it has no abstract methods | |
|
||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
||||
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged. | |
|
||||
| B026 | StarArgUnpackingAfterKeywordArg | Star-arg unpacking after a keyword argument is strongly discouraged | |
|
||||
| B027 | EmptyMethodWithoutAbstractDecorator | `...` is an empty method in an abstract base class, but has no abstract decorator | |
|
||||
|
||||
### flake8-builtins
|
||||
|
||||
@@ -700,7 +710,7 @@ including:
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (21/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (25/32)
|
||||
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
|
||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (15/34)
|
||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||
@@ -725,7 +735,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||
- [`flake8-annotations`](https://pypi.org/project/flake8-annotations/)
|
||||
- [`flake8-bandit`](https://pypi.org/project/flake8-bandit/) (6/40)
|
||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (21/32)
|
||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (26/32)
|
||||
- [`flake8-2020`](https://pypi.org/project/flake8-2020/)
|
||||
|
||||
Ruff can also replace [`isort`](https://pypi.org/project/isort/), [`yesqa`](https://github.com/asottile/yesqa),
|
||||
@@ -751,9 +761,8 @@ project. See [#283](https://github.com/charliermarsh/ruff/issues/283) for more.
|
||||
|
||||
### How does Ruff's import sorting compare to [`isort`](https://pypi.org/project/isort/)?
|
||||
|
||||
Ruff's import sorting is intended to be equivalent to `isort` when used `profile = "black"`, and a
|
||||
few other settings (`combine_as_imports = true`, `order_by_type = false`, and
|
||||
`case_sensitive` = true`).
|
||||
Ruff's import sorting is intended to be nearly equivalent to `isort` when used `profile = "black"`.
|
||||
(There are some minor differences in how Ruff and isort break ties between similar imports.)
|
||||
|
||||
Like `isort`, Ruff's import sorting is compatible with Black.
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use ropey::Rope;
|
||||
use ruff::fs;
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let contents = fs::read_file(Path::new("resources/test/fixtures/D.py")).unwrap();
|
||||
let contents = fs::read_to_string(Path::new("resources/test/fixtures/D.py")).unwrap();
|
||||
c.bench_function("rope", |b| {
|
||||
b.iter(|| {
|
||||
let rope = Rope::from_str(black_box(&contents));
|
||||
|
||||
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.115"
|
||||
version = "0.0.121"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -1975,7 +1975,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.0.115"
|
||||
version = "0.0.121"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "flake8-to-ruff"
|
||||
version = "0.0.115-dev.0"
|
||||
version = "0.0.121-dev.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
@@ -10,6 +10,7 @@ name = "flake8_to_ruff"
|
||||
anyhow = { version = "1.0.66" }
|
||||
clap = { version = "4.0.1", features = ["derive"] }
|
||||
configparser = { version = "3.0.2" }
|
||||
fnv = { version = "1.0.7" }
|
||||
once_cell = { version = "1.16.0" }
|
||||
regex = { version = "1.6.0" }
|
||||
ruff = { path = "..", default-features = false }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use fnv::FnvHashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use ruff::checks_gen::CheckCodePrefix;
|
||||
@@ -179,8 +179,8 @@ pub fn parse_files_to_codes_mapping(value: &str) -> Result<Vec<PatternPrefixPair
|
||||
/// Collect a list of `PatternPrefixPair` structs as a `BTreeMap`.
|
||||
pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
) -> BTreeMap<String, Vec<CheckCodePrefix>> {
|
||||
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
|
||||
) -> FnvHashMap<String, Vec<CheckCodePrefix>> {
|
||||
let mut per_file_ignores: FnvHashMap<String, Vec<CheckCodePrefix>> = FnvHashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
|
||||
107
resources/test/fixtures/B012.py
vendored
Normal file
107
resources/test/fixtures/B012.py
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
def a():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
return # warning
|
||||
|
||||
|
||||
def b():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
if 1 + 0 == 2 - 1:
|
||||
return # warning
|
||||
|
||||
|
||||
def c():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
return # warning
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def d():
|
||||
try:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
return # warning
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
def e():
|
||||
if 1 == 2 - 1:
|
||||
try:
|
||||
|
||||
def f():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
return # warning
|
||||
|
||||
finally:
|
||||
pass
|
||||
|
||||
|
||||
def g():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
|
||||
def h():
|
||||
return # no warning
|
||||
|
||||
e()
|
||||
|
||||
|
||||
def i():
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
break # warning
|
||||
|
||||
def j():
|
||||
while True:
|
||||
break # no warning
|
||||
|
||||
|
||||
def h():
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
continue # warning
|
||||
|
||||
def j():
|
||||
while True:
|
||||
continue # no warning
|
||||
|
||||
|
||||
def k():
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
while True:
|
||||
break # no warning
|
||||
while True:
|
||||
continue # no warning
|
||||
while True:
|
||||
return # warning
|
||||
|
||||
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
continue # warning
|
||||
|
||||
while True:
|
||||
try:
|
||||
pass
|
||||
finally:
|
||||
break # warning
|
||||
40
resources/test/fixtures/B020.py
vendored
Normal file
40
resources/test/fixtures/B020.py
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
"""
|
||||
Should emit:
|
||||
B020 - on lines 8, 21, and 36
|
||||
"""
|
||||
|
||||
items = [1, 2, 3]
|
||||
|
||||
for items in items:
|
||||
print(items)
|
||||
|
||||
items = [1, 2, 3]
|
||||
|
||||
for item in items:
|
||||
print(item)
|
||||
|
||||
values = {"secret": 123}
|
||||
|
||||
for key, value in values.items():
|
||||
print(f"{key}, {value}")
|
||||
|
||||
for key, values in values.items():
|
||||
print(f"{key}, {values}")
|
||||
|
||||
# Variables defined in a comprehension are local in scope
|
||||
# to that comprehension and are therefore allowed.
|
||||
for var in [var for var in range(10)]:
|
||||
print(var)
|
||||
|
||||
for var in (var for var in range(10)):
|
||||
print(var)
|
||||
|
||||
for k, v in {k: v for k, v in zip(range(10), range(10, 20))}.items():
|
||||
print(k, v)
|
||||
|
||||
# However we still call out reassigning the iterable in the comprehension.
|
||||
for vars in [i for i in vars]:
|
||||
print(vars)
|
||||
|
||||
for var in sorted(range(10), key=lambda var: var.real):
|
||||
print(var)
|
||||
76
resources/test/fixtures/B021.py
vendored
Normal file
76
resources/test/fixtures/B021.py
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
f"""
|
||||
Should emit:
|
||||
B021 - on lines 14, 22, 30, 38, 46, 54, 62, 70, 73
|
||||
"""
|
||||
|
||||
VARIABLE = "world"
|
||||
|
||||
|
||||
def foo1():
|
||||
"""hello world!"""
|
||||
|
||||
|
||||
def foo2():
|
||||
f"""hello {VARIABLE}!"""
|
||||
|
||||
|
||||
class bar1:
|
||||
"""hello world!"""
|
||||
|
||||
|
||||
class bar2:
|
||||
f"""hello {VARIABLE}!"""
|
||||
|
||||
|
||||
def foo1():
|
||||
"""hello world!"""
|
||||
|
||||
|
||||
def foo2():
|
||||
f"""hello {VARIABLE}!"""
|
||||
|
||||
|
||||
class bar1:
|
||||
"""hello world!"""
|
||||
|
||||
|
||||
class bar2:
|
||||
f"""hello {VARIABLE}!"""
|
||||
|
||||
|
||||
def foo1():
|
||||
"hello world!"
|
||||
|
||||
|
||||
def foo2():
|
||||
f"hello {VARIABLE}!"
|
||||
|
||||
|
||||
class bar1:
|
||||
"hello world!"
|
||||
|
||||
|
||||
class bar2:
|
||||
f"hello {VARIABLE}!"
|
||||
|
||||
|
||||
def foo1():
|
||||
"hello world!"
|
||||
|
||||
|
||||
def foo2():
|
||||
f"hello {VARIABLE}!"
|
||||
|
||||
|
||||
class bar1:
|
||||
"hello world!"
|
||||
|
||||
|
||||
class bar2:
|
||||
f"hello {VARIABLE}!"
|
||||
|
||||
|
||||
def baz():
|
||||
f"""I'm probably a docstring: {VARIABLE}!"""
|
||||
print(f"""I'm a normal string""")
|
||||
f"""Don't detect me!"""
|
||||
23
resources/test/fixtures/B022.py
vendored
Normal file
23
resources/test/fixtures/B022.py
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Should emit:
|
||||
B022 - on lines 8
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
from contextlib import suppress
|
||||
|
||||
with contextlib.suppress():
|
||||
raise ValueError
|
||||
|
||||
with suppress():
|
||||
raise ValueError
|
||||
|
||||
with contextlib.suppress(ValueError):
|
||||
raise ValueError
|
||||
|
||||
exceptions_to_suppress = []
|
||||
if True:
|
||||
exceptions_to_suppress.append(ValueError)
|
||||
|
||||
with contextlib.suppress(*exceptions_to_suppress):
|
||||
raise
|
||||
129
resources/test/fixtures/B024.py
vendored
Normal file
129
resources/test/fixtures/B024.py
vendored
Normal file
@@ -0,0 +1,129 @@
|
||||
"""
|
||||
Should emit:
|
||||
B024 - on lines 17, 34, 52, 58, 69, 74, 79, 84, 89
|
||||
"""
|
||||
|
||||
import abc
|
||||
import abc as notabc
|
||||
from abc import ABC, ABCMeta
|
||||
from abc import abstractmethod
|
||||
from abc import abstractmethod as abstract
|
||||
from abc import abstractmethod as abstractaoeuaoeuaoeu
|
||||
from abc import abstractmethod as notabstract
|
||||
|
||||
import foo
|
||||
|
||||
|
||||
class Base_1(ABC): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_2(ABC):
|
||||
@abstractmethod
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_3(ABC):
|
||||
@abc.abstractmethod
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_4(ABC):
|
||||
@notabc.abstractmethod
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_5(ABC):
|
||||
@abstract
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_6(ABC):
|
||||
@abstractaoeuaoeuaoeu
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class Base_7(ABC): # error
|
||||
@notabstract
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class MetaBase_1(metaclass=ABCMeta): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class MetaBase_2(metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class abc_Base_1(abc.ABC): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class abc_Base_2(metaclass=abc.ABCMeta): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class notabc_Base_1(notabc.ABC): # error
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class multi_super_1(notabc.ABC, abc.ABCMeta): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class multi_super_2(notabc.ABC, metaclass=abc.ABCMeta): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class non_keyword_abcmeta_1(ABCMeta): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class non_keyword_abcmeta_2(abc.ABCMeta): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
# very invalid code, but that's up to mypy et al to check
|
||||
class keyword_abc_1(metaclass=ABC): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class keyword_abc_2(metaclass=abc.ABC): # safe
|
||||
def method(self):
|
||||
foo()
|
||||
|
||||
|
||||
class abc_set_class_variable_1(ABC): # safe
|
||||
foo: int
|
||||
|
||||
|
||||
class abc_set_class_variable_2(ABC): # safe
|
||||
foo = 2
|
||||
|
||||
|
||||
class abc_set_class_variable_3(ABC): # safe
|
||||
foo: int = 2
|
||||
|
||||
|
||||
# this doesn't actually declare a class variable, it's just an expression
|
||||
class abc_set_class_variable_4(ABC): # error
|
||||
foo
|
||||
88
resources/test/fixtures/B027.py
vendored
Normal file
88
resources/test/fixtures/B027.py
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Should emit:
|
||||
B027 - on lines 12, 15, 18, 22, 30
|
||||
"""
|
||||
import abc
|
||||
from abc import ABC
|
||||
from abc import abstractmethod
|
||||
from abc import abstractmethod as notabstract
|
||||
|
||||
|
||||
class AbstractClass(ABC):
|
||||
def empty_1(self): # error
|
||||
...
|
||||
|
||||
def empty_2(self): # error
|
||||
pass
|
||||
|
||||
def empty_3(self): # error
|
||||
"""docstring"""
|
||||
...
|
||||
|
||||
def empty_4(self): # error
|
||||
"""multiple ellipsis/pass"""
|
||||
...
|
||||
pass
|
||||
...
|
||||
pass
|
||||
|
||||
@notabstract
|
||||
def abstract_0(self):
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def abstract_1(self):
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def abstract_2(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def abstract_3(self):
|
||||
...
|
||||
|
||||
def body_1(self):
|
||||
print("foo")
|
||||
...
|
||||
|
||||
def body_2(self):
|
||||
self.body_1()
|
||||
|
||||
|
||||
class NonAbstractClass:
|
||||
def empty_1(self): # safe
|
||||
...
|
||||
|
||||
def empty_2(self): # safe
|
||||
pass
|
||||
|
||||
|
||||
# ignore @overload, fixes issue #304
|
||||
# ignore overload with other imports, fixes #308
|
||||
import typing
|
||||
import typing as t
|
||||
import typing as anything
|
||||
from typing import Union, overload
|
||||
|
||||
|
||||
class AbstractClass(ABC):
|
||||
@overload
|
||||
def empty_1(self, foo: str):
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
def empty_1(self, foo: int):
|
||||
...
|
||||
|
||||
@t.overload
|
||||
def empty_1(self, foo: list):
|
||||
...
|
||||
|
||||
@anything.overload
|
||||
def empty_1(self, foo: float):
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def empty_1(self, foo: Union[str, int, list, float]):
|
||||
...
|
||||
20
resources/test/fixtures/F401_6.py
vendored
Normal file
20
resources/test/fixtures/F401_6.py
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Test: explicit re-export."""
|
||||
|
||||
# OK
|
||||
from .applications import FastAPI as FastAPI
|
||||
|
||||
# F401 `background.BackgroundTasks` imported but unused
|
||||
from .background import BackgroundTasks
|
||||
|
||||
# F401 `datastructures.UploadFile` imported but unused
|
||||
from .datastructures import UploadFile as FileUpload
|
||||
|
||||
|
||||
# OK
|
||||
import applications as applications
|
||||
|
||||
# F401 `background` imported but unused
|
||||
import background
|
||||
|
||||
# F401 `datastructures` imported but unused
|
||||
import datastructures as structures
|
||||
24
resources/test/fixtures/F821_4.py
vendored
Normal file
24
resources/test/fixtures/F821_4.py
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
"""Test: import alias tracking."""
|
||||
from typing import List
|
||||
|
||||
_ = List["Model"]
|
||||
|
||||
|
||||
from typing import List as IList
|
||||
|
||||
_ = IList["Model"]
|
||||
|
||||
|
||||
from collections.abc import ItemsView
|
||||
|
||||
_ = ItemsView["Model"]
|
||||
|
||||
|
||||
import collections.abc
|
||||
|
||||
_ = collections.abc.ItemsView["Model"]
|
||||
|
||||
|
||||
from collections import abc
|
||||
|
||||
_ = abc.ItemsView["Model"]
|
||||
14
resources/test/fixtures/F821_5.py
vendored
Normal file
14
resources/test/fixtures/F821_5.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Test: inner class annotation."""
|
||||
|
||||
|
||||
class RandomClass:
|
||||
def random_func(self) -> "InnerClass":
|
||||
pass
|
||||
|
||||
|
||||
class OuterClass:
|
||||
class InnerClass:
|
||||
pass
|
||||
|
||||
def failing_func(self) -> "InnerClass":
|
||||
return self.InnerClass()
|
||||
6
resources/test/fixtures/N806.py
vendored
6
resources/test/fixtures/N806.py
vendored
@@ -1,5 +1,11 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
def f():
|
||||
lower = 0
|
||||
Camel = 0
|
||||
CONSTANT = 0
|
||||
_ = 0
|
||||
MyObj1 = collections.namedtuple("MyObj1", ["a", "b"])
|
||||
MyObj2 = namedtuple("MyObj12", ["a", "b"])
|
||||
|
||||
6
resources/test/fixtures/N815.py
vendored
6
resources/test/fixtures/N815.py
vendored
@@ -1,6 +1,12 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
class C:
|
||||
lower = 0
|
||||
CONSTANT = 0
|
||||
mixedCase = 0
|
||||
_mixedCase = 0
|
||||
mixed_Case = 0
|
||||
myObj1 = collections.namedtuple("MyObj1", ["a", "b"])
|
||||
myObj2 = namedtuple("MyObj2", ["a", "b"])
|
||||
|
||||
5
resources/test/fixtures/N816.py
vendored
5
resources/test/fixtures/N816.py
vendored
@@ -1,5 +1,10 @@
|
||||
import collections
|
||||
from collections import namedtuple
|
||||
|
||||
lower = 0
|
||||
CONSTANT = 0
|
||||
mixedCase = 0
|
||||
_mixedCase = 0
|
||||
mixed_Case = 0
|
||||
myObj1 = collections.namedtuple("MyObj1", ["a", "b"])
|
||||
myObj2 = namedtuple("MyObj2", ["a", "b"])
|
||||
|
||||
18
resources/test/fixtures/U006.py
vendored
18
resources/test/fixtures/U006.py
vendored
@@ -1,3 +1,10 @@
|
||||
import typing
|
||||
|
||||
|
||||
def f(x: typing.List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import List
|
||||
|
||||
|
||||
@@ -5,8 +12,15 @@ def f(x: List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
import typing
|
||||
import typing as t
|
||||
|
||||
|
||||
def f(x: typing.List[str]) -> None:
|
||||
def f(x: t.List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
from typing import List as IList
|
||||
|
||||
|
||||
def f(x: IList[str]) -> None:
|
||||
...
|
||||
|
||||
3
resources/test/fixtures/YTT101.py
vendored
3
resources/test/fixtures/YTT101.py
vendored
@@ -5,9 +5,8 @@ print(sys.version)
|
||||
|
||||
print(sys.version[:3])
|
||||
print(version[:3])
|
||||
|
||||
# ignore from imports with aliases, patches welcome
|
||||
print(v[:3])
|
||||
|
||||
# the tool is timid and only flags certain numeric slices
|
||||
i = 3
|
||||
print(sys.version[:i])
|
||||
|
||||
26
resources/test/fixtures/isort/sort_similar_imports.py
vendored
Normal file
26
resources/test/fixtures/isort/sort_similar_imports.py
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
from a import b
|
||||
from a import BAD as DEF
|
||||
from a import B
|
||||
from a import Boo as DEF
|
||||
from a import B as Abc
|
||||
from a import B as A
|
||||
from a import B as DEF
|
||||
from a import b as a
|
||||
from a import b as x
|
||||
from a import b as c
|
||||
from b import c
|
||||
from a import b as d
|
||||
from a import b as y
|
||||
from b import C
|
||||
from b import c as d
|
||||
|
||||
import A
|
||||
import a
|
||||
import b
|
||||
import B
|
||||
|
||||
import x as y
|
||||
import x as A
|
||||
import x as Y
|
||||
import x
|
||||
import x as a
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_dev"
|
||||
version = "0.0.115"
|
||||
version = "0.0.121"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
use fnv::FnvHashSet;
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use rustpython_ast::{Excepthandler, ExcepthandlerKind, Expr, ExprKind, Location, StmtKind};
|
||||
|
||||
fn compose_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
#[inline(always)]
|
||||
fn collect_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, .. } => {
|
||||
compose_call_path_inner(func, parts);
|
||||
collect_call_path_inner(func, parts);
|
||||
}
|
||||
ExprKind::Attribute { value, attr, .. } => {
|
||||
compose_call_path_inner(value, parts);
|
||||
collect_call_path_inner(value, parts);
|
||||
parts.push(attr);
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
@@ -19,9 +20,10 @@ fn compose_call_path_inner<'a>(expr: &'a Expr, parts: &mut Vec<&'a str>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an `Expr` to its call path (like `List`, or `typing.List`).
|
||||
#[inline(always)]
|
||||
pub fn compose_call_path(expr: &Expr) -> Option<String> {
|
||||
let mut segments = vec![];
|
||||
compose_call_path_inner(expr, &mut segments);
|
||||
let segments = collect_call_paths(expr);
|
||||
if segments.is_empty() {
|
||||
None
|
||||
} else {
|
||||
@@ -29,6 +31,34 @@ pub fn compose_call_path(expr: &Expr) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert an `Expr` to its call path segments (like ["typing", "List"]).
|
||||
#[inline(always)]
|
||||
pub fn collect_call_paths(expr: &Expr) -> Vec<&str> {
|
||||
let mut segments = vec![];
|
||||
collect_call_path_inner(expr, &mut segments);
|
||||
segments
|
||||
}
|
||||
|
||||
/// Rewrite any import aliases on a call path.
|
||||
pub fn dealias_call_path<'a>(
|
||||
call_path: Vec<&'a str>,
|
||||
import_aliases: &FnvHashMap<&str, &'a str>,
|
||||
) -> Vec<&'a str> {
|
||||
if let Some(head) = call_path.first() {
|
||||
if let Some(origin) = import_aliases.get(head) {
|
||||
let tail = &call_path[1..];
|
||||
let mut call_path: Vec<&str> = vec![];
|
||||
call_path.extend(origin.split('.'));
|
||||
call_path.extend(tail);
|
||||
call_path
|
||||
} else {
|
||||
call_path
|
||||
}
|
||||
} else {
|
||||
call_path
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a name or attribute reference to `${target}`.
|
||||
pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
match &expr.node {
|
||||
@@ -42,24 +72,87 @@ pub fn match_name_or_attr(expr: &Expr, target: &str) -> bool {
|
||||
///
|
||||
/// Useful for, e.g., ensuring that a `Union` reference represents
|
||||
/// `typing.Union`.
|
||||
pub fn match_name_or_attr_from_module(
|
||||
pub fn match_module_member(
|
||||
expr: &Expr,
|
||||
target: &str,
|
||||
module: &str,
|
||||
imports: Option<&FnvHashSet<&str>>,
|
||||
member: &str,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { value, attr, .. } => match &value.node {
|
||||
ExprKind::Name { id, .. } => id == module && target == attr,
|
||||
_ => false,
|
||||
},
|
||||
ExprKind::Name { id, .. } => {
|
||||
target == id
|
||||
&& imports
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
match_call_path(
|
||||
&dealias_call_path(collect_call_paths(expr), import_aliases),
|
||||
module,
|
||||
member,
|
||||
from_imports,
|
||||
)
|
||||
}
|
||||
|
||||
/// Return `true` if the `call_path` is a reference to `${module}.${target}`.
|
||||
///
|
||||
/// Optimized version of `match_module_member` for pre-computed call paths.
|
||||
pub fn match_call_path(
|
||||
call_path: &[&str],
|
||||
module: &str,
|
||||
member: &str,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
) -> bool {
|
||||
// If we have no segments, we can't ever match.
|
||||
let num_segments = call_path.len();
|
||||
if num_segments == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the last segment doesn't match the member, we can't ever match.
|
||||
if call_path[num_segments - 1] != member {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We now only need the module path, so throw out the member name.
|
||||
let call_path = &call_path[..num_segments - 1];
|
||||
let num_segments = call_path.len();
|
||||
|
||||
// Case (1): It's a builtin (like `list`).
|
||||
// Case (2a): We imported from the parent (`from typing.re import Match`,
|
||||
// `Match`).
|
||||
// Case (2b): We imported star from the parent (`from typing.re import *`,
|
||||
// `Match`).
|
||||
if num_segments == 0 {
|
||||
module.is_empty()
|
||||
|| from_imports
|
||||
.get(module)
|
||||
.map(|imports| imports.contains(member) || imports.contains("*"))
|
||||
.unwrap_or(false)
|
||||
} else {
|
||||
let components: Vec<&str> = module.split('.').collect();
|
||||
|
||||
// Case (3a): it's a fully qualified call path (`import typing`,
|
||||
// `typing.re.Match`). Case (3b): it's a fully qualified call path (`import
|
||||
// typing.re`, `typing.re.Match`).
|
||||
if components == call_path {
|
||||
return true;
|
||||
}
|
||||
_ => false,
|
||||
|
||||
// Case (4): We imported from the grandparent (`from typing import re`,
|
||||
// `re.Match`)
|
||||
let num_matches = (0..components.len())
|
||||
.take(num_segments)
|
||||
.take_while(|i| components[components.len() - 1 - i] == call_path[num_segments - 1 - i])
|
||||
.count();
|
||||
if num_matches > 0 {
|
||||
let cut = components.len() - num_matches;
|
||||
// TODO(charlie): Rewrite to avoid this allocation.
|
||||
let module = components[..cut].join(".");
|
||||
let member = components[cut];
|
||||
if from_imports
|
||||
.get(&module.as_str())
|
||||
.map(|imports| imports.contains(member))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +189,7 @@ pub fn is_assignment_to_a_dunder(node: &StmtKind) -> bool {
|
||||
}
|
||||
|
||||
/// Extract the names of all handled exceptions.
|
||||
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<String> {
|
||||
pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<Vec<&str>> {
|
||||
let mut handler_names = vec![];
|
||||
for handler in handlers {
|
||||
match &handler.node {
|
||||
@@ -104,12 +197,16 @@ pub fn extract_handler_names(handlers: &[Excepthandler]) -> Vec<String> {
|
||||
if let Some(type_) = type_ {
|
||||
if let ExprKind::Tuple { elts, .. } = &type_.node {
|
||||
for type_ in elts {
|
||||
if let Some(name) = compose_call_path(type_) {
|
||||
handler_names.push(name);
|
||||
let call_path = collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
handler_names.push(call_path);
|
||||
}
|
||||
}
|
||||
} else if let Some(name) = compose_call_path(type_) {
|
||||
handler_names.push(name);
|
||||
} else {
|
||||
let call_path = collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
handler_names.push(call_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,3 +237,155 @@ pub fn to_absolute(relative: &Location, base: &Location) -> Location {
|
||||
Location::new(relative.row() + base.row() - 1, relative.column())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use anyhow::Result;
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
|
||||
#[test]
|
||||
fn builtin() -> Result<()> {
|
||||
let expr = parser::parse_expression("list", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"",
|
||||
"list",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::default(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fully_qualified() -> Result<()> {
|
||||
let expr = parser::parse_expression("typing.re.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::default(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unimported() -> Result<()> {
|
||||
let expr = parser::parse_expression("Match", "<filename>")?;
|
||||
assert!(!match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::default(),
|
||||
));
|
||||
let expr = parser::parse_expression("re.Match", "<filename>")?;
|
||||
assert!(!match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::default(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_star() -> Result<()> {
|
||||
let expr = parser::parse_expression("Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing.re", FnvHashSet::from_iter(["*"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_parent() -> Result<()> {
|
||||
let expr = parser::parse_expression("Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing.re", FnvHashSet::from_iter(["Match"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_grandparent() -> Result<()> {
|
||||
let expr = parser::parse_expression("re.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing", FnvHashSet::from_iter(["re"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
|
||||
let expr = parser::parse_expression("match.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re.match",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing.re", FnvHashSet::from_iter(["match"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
|
||||
let expr = parser::parse_expression("re.match.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re.match",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing", FnvHashSet::from_iter(["re"]))]),
|
||||
&FnvHashMap::default()
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_alias() -> Result<()> {
|
||||
let expr = parser::parse_expression("IMatch", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::from_iter([("typing.re", FnvHashSet::from_iter(["Match"]))]),
|
||||
&FnvHashMap::from_iter([("IMatch", "Match")]),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_aliased_parent() -> Result<()> {
|
||||
let expr = parser::parse_expression("t.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::from_iter([("t", "typing.re")]),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_aliased_grandparent() -> Result<()> {
|
||||
let expr = parser::parse_expression("t.re.Match", "<filename>")?;
|
||||
assert!(match_module_member(
|
||||
&expr,
|
||||
"typing.re",
|
||||
"Match",
|
||||
&FnvHashMap::default(),
|
||||
&FnvHashMap::from_iter([("t", "typing")]),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,9 +67,8 @@ pub fn extract_all_names(stmt: &Stmt, scope: &Scope) -> Vec<String> {
|
||||
}
|
||||
|
||||
/// Check if a node is parent of a conditional branch.
|
||||
pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
pub fn on_conditional_branch<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
|
||||
parents.any(|parent| {
|
||||
if matches!(parent.node, StmtKind::If { .. } | StmtKind::While { .. }) {
|
||||
return true;
|
||||
}
|
||||
@@ -78,24 +77,18 @@ pub fn on_conditional_branch(parent_stack: &[usize], parents: &[&Stmt]) -> bool
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a node is in a nested block.
|
||||
pub fn in_nested_block(parent_stack: &[usize], parents: &[&Stmt]) -> bool {
|
||||
for index in parent_stack.iter().rev() {
|
||||
let parent = parents[*index];
|
||||
if matches!(
|
||||
pub fn in_nested_block<'a>(parents: &mut impl Iterator<Item = &'a Stmt>) -> bool {
|
||||
parents.any(|parent| {
|
||||
matches!(
|
||||
parent.node,
|
||||
StmtKind::Try { .. } | StmtKind::If { .. } | StmtKind::With { .. }
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a node represents an unpacking assignment.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
use rustpython_ast::{Expr, Keyword};
|
||||
use rustpython_parser::ast::{Located, Location};
|
||||
|
||||
@@ -54,7 +54,7 @@ pub struct Scope<'a> {
|
||||
pub id: usize,
|
||||
pub kind: ScopeKind<'a>,
|
||||
pub import_starred: bool,
|
||||
pub values: BTreeMap<String, Binding>,
|
||||
pub values: FnvHashMap<String, Binding>,
|
||||
}
|
||||
|
||||
impl<'a> Scope<'a> {
|
||||
@@ -63,7 +63,7 @@ impl<'a> Scope<'a> {
|
||||
id: id(),
|
||||
kind,
|
||||
import_starred: false,
|
||||
values: BTreeMap::new(),
|
||||
values: FnvHashMap::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ fn apply_fixes<'a>(
|
||||
) -> Cow<'a, str> {
|
||||
let mut output = RopeBuilder::new();
|
||||
let mut last_pos: Location = Location::new(1, 0);
|
||||
let mut applied: BTreeSet<&Patch> = BTreeSet::new();
|
||||
let mut applied: BTreeSet<&Patch> = BTreeSet::default();
|
||||
|
||||
for fix in fixes.sorted_by_key(|fix| fix.patch.location) {
|
||||
// If we already applied an identical fix as part of another correction, skip
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::autofix::fixer;
|
||||
use crate::message::Message;
|
||||
use crate::settings::Settings;
|
||||
|
||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CacheMetadata {
|
||||
@@ -89,7 +89,7 @@ fn cache_key(path: &Path, settings: &Settings, autofix: &fixer::Mode) -> String
|
||||
format!(
|
||||
"{}@{}@{}",
|
||||
path.absolutize().unwrap().to_string_lossy(),
|
||||
VERSION,
|
||||
CARGO_PKG_VERSION,
|
||||
hasher.finish()
|
||||
)
|
||||
}
|
||||
|
||||
240
src/check_ast.rs
240
src/check_ast.rs
@@ -5,6 +5,7 @@ use std::ops::Deref;
|
||||
use std::path::Path;
|
||||
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use itertools::Itertools;
|
||||
use log::error;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprContext, ExprKind,
|
||||
@@ -12,7 +13,9 @@ use rustpython_parser::ast::{
|
||||
};
|
||||
use rustpython_parser::parser;
|
||||
|
||||
use crate::ast::helpers::{extract_handler_names, match_name_or_attr_from_module};
|
||||
use crate::ast::helpers::{
|
||||
collect_call_paths, dealias_call_path, extract_handler_names, match_call_path,
|
||||
};
|
||||
use crate::ast::operations::extract_all_names;
|
||||
use crate::ast::relocate::relocate_expr;
|
||||
use crate::ast::types::{
|
||||
@@ -54,6 +57,7 @@ pub struct Checker<'a> {
|
||||
pub(crate) deletions: FnvHashSet<usize>,
|
||||
// Import tracking.
|
||||
pub(crate) from_imports: FnvHashMap<&'a str, FnvHashSet<&'a str>>,
|
||||
pub(crate) import_aliases: FnvHashMap<&'a str, &'a str>,
|
||||
// Retain all scopes and parent nodes, along with a stack of indexes to track which are active
|
||||
// at various points in time.
|
||||
pub(crate) parents: Vec<&'a Stmt>,
|
||||
@@ -61,7 +65,7 @@ pub struct Checker<'a> {
|
||||
scopes: Vec<Scope<'a>>,
|
||||
scope_stack: Vec<usize>,
|
||||
dead_scopes: Vec<usize>,
|
||||
deferred_string_annotations: Vec<(Range, &'a str)>,
|
||||
deferred_string_annotations: Vec<(Range, &'a str, Vec<usize>, Vec<usize>)>,
|
||||
deferred_annotations: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
deferred_functions: Vec<(&'a Stmt, Vec<usize>, Vec<usize>, VisibleScope)>,
|
||||
deferred_lambdas: Vec<(&'a Expr, Vec<usize>, Vec<usize>)>,
|
||||
@@ -75,7 +79,7 @@ pub struct Checker<'a> {
|
||||
seen_import_boundary: bool,
|
||||
futures_allowed: bool,
|
||||
annotations_future_enabled: bool,
|
||||
except_handlers: Vec<Vec<String>>,
|
||||
except_handlers: Vec<Vec<Vec<&'a str>>>,
|
||||
}
|
||||
|
||||
impl<'a> Checker<'a> {
|
||||
@@ -94,6 +98,7 @@ impl<'a> Checker<'a> {
|
||||
definitions: Default::default(),
|
||||
deletions: Default::default(),
|
||||
from_imports: Default::default(),
|
||||
import_aliases: Default::default(),
|
||||
parents: Default::default(),
|
||||
parent_stack: Default::default(),
|
||||
scopes: Default::default(),
|
||||
@@ -154,15 +159,16 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
/// Return `true` if the `Expr` is a reference to `typing.${target}`.
|
||||
pub fn match_typing_module(&self, expr: &Expr, target: &str) -> bool {
|
||||
match_name_or_attr_from_module(expr, target, "typing", self.from_imports.get("typing"))
|
||||
pub fn match_typing_expr(&self, expr: &Expr, target: &str) -> bool {
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), &self.import_aliases);
|
||||
self.match_typing_call_path(&call_path, target)
|
||||
}
|
||||
|
||||
/// Return `true` if the call path is a reference to `typing.${target}`.
|
||||
pub fn match_typing_call_path(&self, call_path: &[&str], target: &str) -> bool {
|
||||
match_call_path(call_path, "typing", target, &self.from_imports)
|
||||
|| (typing::in_extensions(target)
|
||||
&& match_name_or_attr_from_module(
|
||||
expr,
|
||||
target,
|
||||
"typing_extensions",
|
||||
self.from_imports.get("typing_extensions"),
|
||||
))
|
||||
&& match_call_path(call_path, "typing_extensions", target, &self.from_imports))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +198,13 @@ where
|
||||
self.futures_allowed = false;
|
||||
if !self.seen_import_boundary
|
||||
&& !helpers::is_assignment_to_a_dunder(node)
|
||||
&& !operations::in_nested_block(&self.parent_stack, &self.parents)
|
||||
&& !operations::in_nested_block(
|
||||
&mut self
|
||||
.parent_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|index| self.parents[*index]),
|
||||
)
|
||||
{
|
||||
self.seen_import_boundary = true;
|
||||
}
|
||||
@@ -447,6 +459,14 @@ where
|
||||
flake8_bugbear::plugins::useless_expression(self, body);
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::B024)
|
||||
|| self.settings.enabled.contains(&CheckCode::B027)
|
||||
{
|
||||
flake8_bugbear::plugins::abstract_base_class(
|
||||
self, stmt, name, bases, keywords, body,
|
||||
);
|
||||
}
|
||||
|
||||
self.check_builtin_shadowing(name, Range::from_located(stmt), false);
|
||||
|
||||
for expr in bases {
|
||||
@@ -511,13 +531,38 @@ where
|
||||
full_name.to_string(),
|
||||
self.binding_context(),
|
||||
),
|
||||
used: None,
|
||||
// Treat explicit re-export as usage (e.g., `import applications
|
||||
// as applications`).
|
||||
used: if alias
|
||||
.node
|
||||
.asname
|
||||
.as_ref()
|
||||
.map(|asname| asname == &alias.node.name)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some((
|
||||
self.scopes[*(self
|
||||
.scope_stack
|
||||
.last()
|
||||
.expect("No current scope found."))]
|
||||
.id,
|
||||
Range::from_located(stmt),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
range: Range::from_located(stmt),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
for alias in names {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.import_aliases.insert(asname, &alias.node.name);
|
||||
}
|
||||
}
|
||||
|
||||
let name = alias.node.name.split('.').last().unwrap();
|
||||
if self.settings.enabled.contains(&CheckCode::N811) {
|
||||
if let Some(check) =
|
||||
@@ -586,6 +631,11 @@ where
|
||||
.map(|alias| alias.node.name.as_str()),
|
||||
)
|
||||
}
|
||||
for alias in names {
|
||||
if let Some(asname) = &alias.node.asname {
|
||||
self.import_aliases.insert(asname, &alias.node.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::E402) {
|
||||
@@ -610,6 +660,7 @@ where
|
||||
name.to_string(),
|
||||
Binding {
|
||||
kind: BindingKind::FutureImportation,
|
||||
// Always mark `__future__` imports as used.
|
||||
used: Some((
|
||||
self.scopes[*(self
|
||||
.scope_stack
|
||||
@@ -702,7 +753,26 @@ where
|
||||
full_name,
|
||||
self.binding_context(),
|
||||
),
|
||||
used: None,
|
||||
// Treat explicit re-export as usage (e.g., `from .applications
|
||||
// import FastAPI as FastAPI`).
|
||||
used: if alias
|
||||
.node
|
||||
.asname
|
||||
.as_ref()
|
||||
.map(|asname| asname == &alias.node.name)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Some((
|
||||
self.scopes[*(self
|
||||
.scope_stack
|
||||
.last()
|
||||
.expect("No current scope found."))]
|
||||
.id,
|
||||
Range::from_located(stmt),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
range: Range::from_located(stmt),
|
||||
},
|
||||
)
|
||||
@@ -803,10 +873,15 @@ where
|
||||
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
|
||||
}
|
||||
}
|
||||
StmtKind::For { target, body, .. } => {
|
||||
StmtKind::For {
|
||||
target, body, iter, ..
|
||||
} => {
|
||||
if self.settings.enabled.contains(&CheckCode::B007) {
|
||||
flake8_bugbear::plugins::unused_loop_control_variable(self, target, body);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B020) {
|
||||
flake8_bugbear::plugins::loop_variable_overrides_iterator(self, target, iter);
|
||||
}
|
||||
}
|
||||
StmtKind::Try { handlers, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::F707) {
|
||||
@@ -870,6 +945,9 @@ where
|
||||
let prev_visible_scope = self.visible_scope.clone();
|
||||
match &stmt.node {
|
||||
StmtKind::FunctionDef { body, .. } | StmtKind::AsyncFunctionDef { body, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::B021) {
|
||||
flake8_bugbear::plugins::f_string_docstring(self, body);
|
||||
}
|
||||
let definition = docstrings::extraction::extract(
|
||||
&self.visible_scope,
|
||||
stmt,
|
||||
@@ -889,6 +967,9 @@ where
|
||||
));
|
||||
}
|
||||
StmtKind::ClassDef { body, .. } => {
|
||||
if self.settings.enabled.contains(&CheckCode::B021) {
|
||||
flake8_bugbear::plugins::f_string_docstring(self, body);
|
||||
}
|
||||
let definition = docstrings::extraction::extract(
|
||||
&self.visible_scope,
|
||||
stmt,
|
||||
@@ -911,6 +992,9 @@ where
|
||||
finalbody,
|
||||
} => {
|
||||
self.except_handlers.push(extract_handler_names(handlers));
|
||||
if self.settings.enabled.contains(&CheckCode::B012) {
|
||||
flake8_bugbear::plugins::jump_statement_in_finally(self, finalbody);
|
||||
}
|
||||
for stmt in body {
|
||||
self.visit_stmt(stmt);
|
||||
}
|
||||
@@ -963,8 +1047,12 @@ where
|
||||
..
|
||||
} = &expr.node
|
||||
{
|
||||
self.deferred_string_annotations
|
||||
.push((Range::from_located(expr), value));
|
||||
self.deferred_string_annotations.push((
|
||||
Range::from_located(expr),
|
||||
value,
|
||||
self.scope_stack.clone(),
|
||||
self.parent_stack.clone(),
|
||||
));
|
||||
} else {
|
||||
self.deferred_annotations.push((
|
||||
expr,
|
||||
@@ -980,12 +1068,12 @@ where
|
||||
ExprKind::Subscript { value, slice, .. } => {
|
||||
// Ex) typing.List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U007)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
&& self.settings.target_version >= PythonVersion::Py310
|
||||
{
|
||||
pyupgrade::plugins::use_pep604_annotation(self, expr, value, slice);
|
||||
}
|
||||
|
||||
if self.match_typing_module(value, "Literal") {
|
||||
if self.match_typing_expr(value, "Literal") {
|
||||
self.in_literal = true;
|
||||
}
|
||||
|
||||
@@ -1019,7 +1107,11 @@ where
|
||||
// Ex) List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U006)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
&& typing::is_pep585_builtin(expr, self.from_imports.get("typing"))
|
||||
&& typing::is_pep585_builtin(
|
||||
expr,
|
||||
&self.from_imports,
|
||||
&self.import_aliases,
|
||||
)
|
||||
{
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, id);
|
||||
}
|
||||
@@ -1051,7 +1143,7 @@ where
|
||||
// Ex) typing.List[...]
|
||||
if self.settings.enabled.contains(&CheckCode::U006)
|
||||
&& self.settings.target_version >= PythonVersion::Py39
|
||||
&& typing::is_pep585_builtin(expr, self.from_imports.get("typing"))
|
||||
&& typing::is_pep585_builtin(expr, &self.from_imports, &self.import_aliases)
|
||||
{
|
||||
pyupgrade::plugins::use_pep585_annotation(self, expr, attr);
|
||||
}
|
||||
@@ -1104,6 +1196,9 @@ where
|
||||
flake8_bugbear::plugins::setattr_with_constant(self, expr, func, args);
|
||||
}
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B022) {
|
||||
flake8_bugbear::plugins::useless_contextlib_suppress(self, expr, args);
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::B026) {
|
||||
flake8_bugbear::plugins::star_arg_unpacking_after_keyword_arg(
|
||||
self, args, keywords,
|
||||
@@ -1483,8 +1578,12 @@ where
|
||||
..
|
||||
} => {
|
||||
if self.in_annotation && !self.in_literal {
|
||||
self.deferred_string_annotations
|
||||
.push((Range::from_located(expr), value));
|
||||
self.deferred_string_annotations.push((
|
||||
Range::from_located(expr),
|
||||
value,
|
||||
self.scope_stack.clone(),
|
||||
self.parent_stack.clone(),
|
||||
));
|
||||
}
|
||||
if self.settings.enabled.contains(&CheckCode::S104) {
|
||||
if let Some(check) = flake8_bandit::plugins::hardcoded_bind_all_interfaces(
|
||||
@@ -1565,12 +1664,13 @@ where
|
||||
args,
|
||||
keywords,
|
||||
} => {
|
||||
if self.match_typing_module(func, "ForwardRef") {
|
||||
let call_path = dealias_call_path(collect_call_paths(func), &self.import_aliases);
|
||||
if self.match_typing_call_path(&call_path, "ForwardRef") {
|
||||
self.visit_expr(func);
|
||||
for expr in args {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
} else if self.match_typing_module(func, "cast") {
|
||||
} else if self.match_typing_call_path(&call_path, "cast") {
|
||||
self.visit_expr(func);
|
||||
if !args.is_empty() {
|
||||
self.visit_annotation(&args[0]);
|
||||
@@ -1578,12 +1678,12 @@ where
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
} else if self.match_typing_module(func, "NewType") {
|
||||
} else if self.match_typing_call_path(&call_path, "NewType") {
|
||||
self.visit_expr(func);
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_annotation(expr);
|
||||
}
|
||||
} else if self.match_typing_module(func, "TypeVar") {
|
||||
} else if self.match_typing_call_path(&call_path, "TypeVar") {
|
||||
self.visit_expr(func);
|
||||
for expr in args.iter().skip(1) {
|
||||
self.visit_annotation(expr);
|
||||
@@ -1600,7 +1700,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if self.match_typing_module(func, "NamedTuple") {
|
||||
} else if self.match_typing_call_path(&call_path, "NamedTuple") {
|
||||
self.visit_expr(func);
|
||||
|
||||
// Ex) NamedTuple("a", [("a", int)])
|
||||
@@ -1632,7 +1732,7 @@ where
|
||||
let KeywordData { value, .. } = &keyword.node;
|
||||
self.visit_annotation(value);
|
||||
}
|
||||
} else if self.match_typing_module(func, "TypedDict") {
|
||||
} else if self.match_typing_call_path(&call_path, "TypedDict") {
|
||||
self.visit_expr(func);
|
||||
|
||||
// Ex) TypedDict("a", {"a": int})
|
||||
@@ -1671,7 +1771,11 @@ where
|
||||
visitor::walk_expr(self, expr);
|
||||
} else {
|
||||
self.in_subscript = true;
|
||||
match typing::match_annotated_subscript(value, &self.from_imports) {
|
||||
match typing::match_annotated_subscript(
|
||||
value,
|
||||
&self.from_imports,
|
||||
&self.import_aliases,
|
||||
) {
|
||||
Some(subscript) => {
|
||||
match subscript {
|
||||
// Ex) Optional[int]
|
||||
@@ -2037,6 +2141,7 @@ impl<'a> Checker<'a> {
|
||||
let mut import_starred = false;
|
||||
for scope_index in self.scope_stack.iter().rev() {
|
||||
let scope = &mut self.scopes[*scope_index];
|
||||
|
||||
if matches!(scope.kind, ScopeKind::Class(_)) {
|
||||
if id == "__class__" {
|
||||
return;
|
||||
@@ -2083,7 +2188,10 @@ impl<'a> Checker<'a> {
|
||||
|
||||
// Avoid flagging if NameError is handled.
|
||||
if let Some(handler_names) = self.except_handlers.last() {
|
||||
if handler_names.contains(&"NameError".to_string()) {
|
||||
if handler_names
|
||||
.iter()
|
||||
.any(|call_path| call_path.len() == 1 && call_path[0] == "NameError")
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2110,32 +2218,22 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N806) {
|
||||
let current =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::non_lowercase_variable_in_function(current, expr, id)
|
||||
{
|
||||
self.add_check(check);
|
||||
if matches!(self.current_scope().kind, ScopeKind::Function(..)) {
|
||||
pep8_naming::plugins::non_lowercase_variable_in_function(self, expr, parent, id)
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N815) {
|
||||
let current =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::mixed_case_variable_in_class_scope(current, expr, id)
|
||||
{
|
||||
self.add_check(check);
|
||||
if matches!(self.current_scope().kind, ScopeKind::Class(..)) {
|
||||
pep8_naming::plugins::mixed_case_variable_in_class_scope(self, expr, parent, id)
|
||||
}
|
||||
}
|
||||
|
||||
if self.settings.enabled.contains(&CheckCode::N816) {
|
||||
let current =
|
||||
&self.scopes[*(self.scope_stack.last().expect("No current scope found."))];
|
||||
if let Some(check) =
|
||||
pep8_naming::checks::mixed_case_variable_in_global_scope(current, expr, id)
|
||||
{
|
||||
self.add_check(check);
|
||||
if matches!(self.current_scope().kind, ScopeKind::Module) {
|
||||
pep8_naming::plugins::mixed_case_variable_in_global_scope(
|
||||
self, expr, parent, id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2214,7 +2312,13 @@ impl<'a> Checker<'a> {
|
||||
|
||||
fn handle_node_delete(&mut self, expr: &Expr) {
|
||||
if let ExprKind::Name { id, .. } = &expr.node {
|
||||
if operations::on_conditional_branch(&self.parent_stack, &self.parents) {
|
||||
if operations::on_conditional_branch(
|
||||
&mut self
|
||||
.parent_stack
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|index| self.parents[*index]),
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2234,6 +2338,9 @@ impl<'a> Checker<'a> {
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
if self.settings.enabled.contains(&CheckCode::B021) {
|
||||
flake8_bugbear::plugins::f_string_docstring(self, python_ast);
|
||||
}
|
||||
let docstring = docstrings::extraction::docstring_from(python_ast);
|
||||
self.definitions.push((
|
||||
Definition {
|
||||
@@ -2251,8 +2358,8 @@ impl<'a> Checker<'a> {
|
||||
|
||||
fn check_deferred_annotations(&mut self) {
|
||||
while let Some((expr, scopes, parents)) = self.deferred_annotations.pop() {
|
||||
self.parent_stack = parents;
|
||||
self.scope_stack = scopes;
|
||||
self.parent_stack = parents;
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
@@ -2261,10 +2368,14 @@ impl<'a> Checker<'a> {
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
while let Some((range, expression)) = self.deferred_string_annotations.pop() {
|
||||
let mut stacks = vec![];
|
||||
while let Some((range, expression, scopes, parents)) =
|
||||
self.deferred_string_annotations.pop()
|
||||
{
|
||||
if let Ok(mut expr) = parser::parse_expression(expression, "<filename>") {
|
||||
relocate_expr(&mut expr, range);
|
||||
allocator.push(expr);
|
||||
stacks.push((scopes, parents));
|
||||
} else {
|
||||
if self.settings.enabled.contains(&CheckCode::F722) {
|
||||
self.add_check(Check::new(
|
||||
@@ -2274,7 +2385,9 @@ impl<'a> Checker<'a> {
|
||||
}
|
||||
}
|
||||
}
|
||||
for expr in allocator {
|
||||
for (expr, (scopes, parents)) in allocator.iter().zip(stacks) {
|
||||
self.scope_stack = scopes;
|
||||
self.parent_stack = parents;
|
||||
self.visit_expr(expr);
|
||||
}
|
||||
}
|
||||
@@ -2400,7 +2513,7 @@ impl<'a> Checker<'a> {
|
||||
let mut unused: BTreeMap<(ImportKind, usize, Option<usize>), Vec<&str>> =
|
||||
BTreeMap::new();
|
||||
|
||||
for (name, binding) in scope.values.iter().rev() {
|
||||
for (name, binding) in scope.values.iter() {
|
||||
let used = binding.used.is_some()
|
||||
|| all_names
|
||||
.map(|names| names.contains(name))
|
||||
@@ -2409,25 +2522,25 @@ impl<'a> Checker<'a> {
|
||||
if !used {
|
||||
match &binding.kind {
|
||||
BindingKind::FromImportation(_, full_name, context) => {
|
||||
let full_names = unused
|
||||
unused
|
||||
.entry((
|
||||
ImportKind::ImportFrom,
|
||||
context.defined_by,
|
||||
context.defined_in,
|
||||
))
|
||||
.or_default();
|
||||
full_names.push(full_name);
|
||||
.or_default()
|
||||
.push(full_name);
|
||||
}
|
||||
BindingKind::Importation(_, full_name, context)
|
||||
| BindingKind::SubmoduleImportation(_, full_name, context) => {
|
||||
let full_names = unused
|
||||
unused
|
||||
.entry((
|
||||
ImportKind::Import,
|
||||
context.defined_by,
|
||||
context.defined_in,
|
||||
))
|
||||
.or_default();
|
||||
full_names.push(full_name);
|
||||
.or_default()
|
||||
.push(full_name);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -2468,7 +2581,7 @@ impl<'a> Checker<'a> {
|
||||
if self.path.ends_with("__init__.py") {
|
||||
checks.push(Check::new(
|
||||
CheckKind::UnusedImport(
|
||||
full_names.into_iter().map(String::from).collect(),
|
||||
full_names.into_iter().sorted().map(String::from).collect(),
|
||||
true,
|
||||
),
|
||||
Range::from_located(child),
|
||||
@@ -2476,7 +2589,7 @@ impl<'a> Checker<'a> {
|
||||
} else {
|
||||
let mut check = Check::new(
|
||||
CheckKind::UnusedImport(
|
||||
full_names.into_iter().map(String::from).collect(),
|
||||
full_names.into_iter().sorted().map(String::from).collect(),
|
||||
false,
|
||||
),
|
||||
Range::from_located(child),
|
||||
@@ -2669,8 +2782,5 @@ pub fn check_ast(
|
||||
// Check docstrings.
|
||||
checker.check_definitions();
|
||||
|
||||
// Check import blocks.
|
||||
// checker.check_import_blocks();
|
||||
|
||||
checker.checks
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ mod tests {
|
||||
},
|
||||
&fixer::Mode::Generate,
|
||||
);
|
||||
return checks;
|
||||
checks
|
||||
};
|
||||
assert!(!check_with_max_line_length(6).is_empty());
|
||||
assert!(check_with_max_line_length(7).is_empty());
|
||||
|
||||
118
src/checks.rs
118
src/checks.rs
@@ -87,6 +87,7 @@ pub enum CheckCode {
|
||||
B009,
|
||||
B010,
|
||||
B011,
|
||||
B012,
|
||||
B013,
|
||||
B014,
|
||||
B015,
|
||||
@@ -94,8 +95,13 @@ pub enum CheckCode {
|
||||
B017,
|
||||
B018,
|
||||
B019,
|
||||
B020,
|
||||
B021,
|
||||
B022,
|
||||
B024,
|
||||
B025,
|
||||
B026,
|
||||
B027,
|
||||
// flake8-comprehensions
|
||||
C400,
|
||||
C401,
|
||||
@@ -382,10 +388,11 @@ pub enum CheckKind {
|
||||
StripWithMultiCharacters,
|
||||
MutableArgumentDefault,
|
||||
UnusedLoopControlVariable(String),
|
||||
FunctionCallArgumentDefault,
|
||||
FunctionCallArgumentDefault(Option<String>),
|
||||
GetAttrWithConstant,
|
||||
SetAttrWithConstant,
|
||||
DoNotAssertFalse,
|
||||
JumpStatementInFinally(String),
|
||||
RedundantTupleInExceptionHandler(String),
|
||||
DuplicateHandlerException(Vec<String>),
|
||||
UselessComparison,
|
||||
@@ -393,8 +400,13 @@ pub enum CheckKind {
|
||||
NoAssertRaisesException,
|
||||
UselessExpression,
|
||||
CachedInstanceMethod,
|
||||
LoopVariableOverridesIterator(String),
|
||||
FStringDocstring,
|
||||
UselessContextlibSuppress,
|
||||
AbstractBaseClassWithoutAbstractMethod(String),
|
||||
DuplicateTryBlockException(String),
|
||||
StarArgUnpackingAfterKeywordArg,
|
||||
EmptyMethodWithoutAbstractDecorator(String),
|
||||
// flake8-comprehensions
|
||||
UnnecessaryGeneratorList,
|
||||
UnnecessaryGeneratorSet,
|
||||
@@ -619,10 +631,13 @@ impl CheckCode {
|
||||
CheckCode::B005 => CheckKind::StripWithMultiCharacters,
|
||||
CheckCode::B006 => CheckKind::MutableArgumentDefault,
|
||||
CheckCode::B007 => CheckKind::UnusedLoopControlVariable("i".to_string()),
|
||||
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault,
|
||||
CheckCode::B008 => CheckKind::FunctionCallArgumentDefault(None),
|
||||
CheckCode::B009 => CheckKind::GetAttrWithConstant,
|
||||
CheckCode::B010 => CheckKind::SetAttrWithConstant,
|
||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||
CheckCode::B012 => {
|
||||
CheckKind::JumpStatementInFinally("return/continue/break".to_string())
|
||||
}
|
||||
CheckCode::B013 => {
|
||||
CheckKind::RedundantTupleInExceptionHandler("ValueError".to_string())
|
||||
}
|
||||
@@ -632,8 +647,13 @@ impl CheckCode {
|
||||
CheckCode::B017 => CheckKind::NoAssertRaisesException,
|
||||
CheckCode::B018 => CheckKind::UselessExpression,
|
||||
CheckCode::B019 => CheckKind::CachedInstanceMethod,
|
||||
CheckCode::B020 => CheckKind::LoopVariableOverridesIterator("...".to_string()),
|
||||
CheckCode::B021 => CheckKind::FStringDocstring,
|
||||
CheckCode::B022 => CheckKind::UselessContextlibSuppress,
|
||||
CheckCode::B024 => CheckKind::AbstractBaseClassWithoutAbstractMethod("...".to_string()),
|
||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||
CheckCode::B026 => CheckKind::StarArgUnpackingAfterKeywordArg,
|
||||
CheckCode::B027 => CheckKind::EmptyMethodWithoutAbstractDecorator("...".to_string()),
|
||||
// flake8-comprehensions
|
||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||
CheckCode::C401 => CheckKind::UnnecessaryGeneratorSet,
|
||||
@@ -865,6 +885,7 @@ impl CheckCode {
|
||||
CheckCode::B009 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B010 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B012 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B013 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B015 => CheckCategory::Flake8Bugbear,
|
||||
@@ -872,8 +893,13 @@ impl CheckCode {
|
||||
CheckCode::B017 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B018 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B019 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B020 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B021 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B022 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B024 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B026 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::B027 => CheckCategory::Flake8Bugbear,
|
||||
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
||||
CheckCode::C402 => CheckCategory::Flake8Comprehensions,
|
||||
@@ -1064,10 +1090,11 @@ impl CheckKind {
|
||||
CheckKind::StripWithMultiCharacters => &CheckCode::B005,
|
||||
CheckKind::MutableArgumentDefault => &CheckCode::B006,
|
||||
CheckKind::UnusedLoopControlVariable(_) => &CheckCode::B007,
|
||||
CheckKind::FunctionCallArgumentDefault => &CheckCode::B008,
|
||||
CheckKind::FunctionCallArgumentDefault(_) => &CheckCode::B008,
|
||||
CheckKind::GetAttrWithConstant => &CheckCode::B009,
|
||||
CheckKind::SetAttrWithConstant => &CheckCode::B010,
|
||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||
CheckKind::JumpStatementInFinally(_) => &CheckCode::B012,
|
||||
CheckKind::RedundantTupleInExceptionHandler(_) => &CheckCode::B013,
|
||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||
CheckKind::UselessComparison => &CheckCode::B015,
|
||||
@@ -1075,8 +1102,13 @@ impl CheckKind {
|
||||
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
||||
CheckKind::UselessExpression => &CheckCode::B018,
|
||||
CheckKind::CachedInstanceMethod => &CheckCode::B019,
|
||||
CheckKind::LoopVariableOverridesIterator(_) => &CheckCode::B020,
|
||||
CheckKind::FStringDocstring => &CheckCode::B021,
|
||||
CheckKind::UselessContextlibSuppress => &CheckCode::B022,
|
||||
CheckKind::AbstractBaseClassWithoutAbstractMethod(_) => &CheckCode::B024,
|
||||
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => &CheckCode::B026,
|
||||
CheckKind::EmptyMethodWithoutAbstractDecorator(_) => &CheckCode::B027,
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
||||
CheckKind::UnnecessaryGeneratorSet => &CheckCode::C401,
|
||||
@@ -1236,7 +1268,7 @@ impl CheckKind {
|
||||
CheckKind::BreakOutsideLoop => "`break` outside loop".to_string(),
|
||||
CheckKind::ContinueOutsideLoop => "`continue` not properly in loop".to_string(),
|
||||
CheckKind::DefaultExceptNotLast => {
|
||||
"An `except:` block as not the last exception handler".to_string()
|
||||
"An `except` block as not the last exception handler".to_string()
|
||||
}
|
||||
CheckKind::DoNotAssignLambda => {
|
||||
"Do not assign a lambda expression, use a def".to_string()
|
||||
@@ -1373,40 +1405,47 @@ impl CheckKind {
|
||||
`+(+(n))`, which equals `n`. You meant `n += 1`."
|
||||
.to_string(),
|
||||
CheckKind::AssignmentToOsEnviron => {
|
||||
"Assigning to `os.environ` doesn't clear the environment.".to_string()
|
||||
"Assigning to `os.environ` doesn't clear the environment".to_string()
|
||||
}
|
||||
CheckKind::UnreliableCallableCheck => " Using `hasattr(x, '__call__')` to test if x \
|
||||
is callable is unreliable. Use `callable(x)` \
|
||||
for consistent results."
|
||||
.to_string(),
|
||||
CheckKind::StripWithMultiCharacters => "Using `.strip()` with multi-character strings \
|
||||
is misleading the reader."
|
||||
.to_string(),
|
||||
CheckKind::StripWithMultiCharacters => {
|
||||
"Using `.strip()` with multi-character strings is misleading the reader".to_string()
|
||||
}
|
||||
CheckKind::MutableArgumentDefault => {
|
||||
"Do not use mutable data structures for argument defaults.".to_string()
|
||||
"Do not use mutable data structures for argument defaults".to_string()
|
||||
}
|
||||
CheckKind::UnusedLoopControlVariable(name) => format!(
|
||||
"Loop control variable `{name}` not used within the loop body. If this is \
|
||||
intended, start the name with an underscore."
|
||||
),
|
||||
CheckKind::FunctionCallArgumentDefault => {
|
||||
"Do not perform function calls in argument defaults.".to_string()
|
||||
CheckKind::FunctionCallArgumentDefault(name) => {
|
||||
if let Some(name) = name {
|
||||
format!("Do not perform function call `{name}` in argument defaults")
|
||||
} else {
|
||||
"Do not perform function call in argument defaults".to_string()
|
||||
}
|
||||
}
|
||||
CheckKind::GetAttrWithConstant => "Do not call `getattr` with a constant attribute \
|
||||
value, it is not any safer than normal property \
|
||||
value. It is not any safer than normal property \
|
||||
access."
|
||||
.to_string(),
|
||||
CheckKind::SetAttrWithConstant => "Do not call `setattr` with a constant attribute \
|
||||
value, it is not any safer than normal property \
|
||||
value. It is not any safer than normal property \
|
||||
access."
|
||||
.to_string(),
|
||||
CheckKind::DoNotAssertFalse => "Do not `assert False` (`python -O` removes these \
|
||||
calls), raise `AssertionError()`"
|
||||
.to_string(),
|
||||
CheckKind::JumpStatementInFinally(name) => {
|
||||
format!("`{name}` inside finally blocks cause exceptions to be silenced")
|
||||
}
|
||||
CheckKind::RedundantTupleInExceptionHandler(name) => {
|
||||
format!(
|
||||
"A length-one tuple literal is redundant. Write `except {name}:` instead of \
|
||||
`except ({name},):`."
|
||||
"A length-one tuple literal is redundant. Write `except {name}` instead of \
|
||||
`except ({name},)`."
|
||||
)
|
||||
}
|
||||
CheckKind::UselessComparison => "Pointless comparison. This comparison does nothing \
|
||||
@@ -1426,7 +1465,7 @@ impl CheckKind {
|
||||
}
|
||||
}
|
||||
CheckKind::NoAssertRaisesException => {
|
||||
"`assertRaises(Exception):` should be considered evil. It can lead to your test \
|
||||
"`assertRaises(Exception)` should be considered evil. It can lead to your test \
|
||||
passing even if the code being tested is never executed due to a typo. Either \
|
||||
assert for a more specific exception (builtin or custom), use \
|
||||
`assertRaisesRegex`, or use the context manager form of `assertRaises`."
|
||||
@@ -1436,18 +1475,37 @@ impl CheckKind {
|
||||
"Found useless expression. Either assign it to a variable or remove it.".to_string()
|
||||
}
|
||||
CheckKind::CachedInstanceMethod => "Use of `functools.lru_cache` or `functools.cache` \
|
||||
on methods can lead to memory leaks."
|
||||
on methods can lead to memory leaks"
|
||||
.to_string(),
|
||||
CheckKind::LoopVariableOverridesIterator(name) => {
|
||||
format!("Loop control variable `{name}` overrides iterable it iterates")
|
||||
}
|
||||
CheckKind::FStringDocstring => "f-string used as docstring. This will be interpreted \
|
||||
by python as a joined string rather than a docstring."
|
||||
.to_string(),
|
||||
CheckKind::UselessContextlibSuppress => {
|
||||
"No arguments passed to `contextlib.suppress`. No exceptions will be suppressed \
|
||||
and therefore this context manager is redundant"
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::AbstractBaseClassWithoutAbstractMethod(name) => {
|
||||
format!("`{name}` is an abstract base class, but it has no abstract methods")
|
||||
}
|
||||
CheckKind::DuplicateTryBlockException(name) => {
|
||||
format!("try-except block with duplicate exception `{name}`")
|
||||
}
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => {
|
||||
"Star-arg unpacking after a keyword argument is strongly discouraged, because it \
|
||||
only works when the keyword parameter is declared after all parameters supplied \
|
||||
by the unpacked sequence, and this change of ordering can surprise and mislead \
|
||||
readers."
|
||||
"Star-arg unpacking after a keyword argument is strongly discouraged. It only \
|
||||
works when the keyword parameter is declared after all parameters supplied by the \
|
||||
unpacked sequence, and this change of ordering can surprise and mislead readers."
|
||||
.to_string()
|
||||
}
|
||||
CheckKind::EmptyMethodWithoutAbstractDecorator(name) => {
|
||||
format!(
|
||||
"`{name}` is an empty method in an abstract base class, but has no abstract \
|
||||
decorator"
|
||||
)
|
||||
}
|
||||
// flake8-comprehensions
|
||||
CheckKind::UnnecessaryGeneratorList => {
|
||||
"Unnecessary generator (rewrite as a `list` comprehension)".to_string()
|
||||
@@ -1703,7 +1761,7 @@ impl CheckKind {
|
||||
CheckKind::PublicNestedClass => "Missing docstring in public nested class".to_string(),
|
||||
CheckKind::PublicInit => "Missing docstring in `__init__`".to_string(),
|
||||
CheckKind::NoThisPrefix => {
|
||||
"First word of the docstring should not be 'This'".to_string()
|
||||
"First word of the docstring should not be \"This\"".to_string()
|
||||
}
|
||||
CheckKind::SkipDocstring => {
|
||||
"Function decorated with `@overload` shouldn't contain a docstring".to_string()
|
||||
@@ -1811,7 +1869,7 @@ impl CheckKind {
|
||||
format!("Exception name `{name}` should be named with an Error suffix")
|
||||
}
|
||||
CheckKind::PEP3120UnnecessaryCodingComment => {
|
||||
"utf-8 encoding declaration is unnecessary".to_string()
|
||||
"UTF-8 encoding declaration is unnecessary".to_string()
|
||||
}
|
||||
// isort
|
||||
CheckKind::UnsortedImports => "Import block is un-sorted or un-formatted".to_string(),
|
||||
@@ -1822,13 +1880,13 @@ impl CheckKind {
|
||||
"Possible binding to all interfaces".to_string()
|
||||
}
|
||||
CheckKind::HardcodedPasswordString(string) => {
|
||||
format!("Possible hardcoded password: `'{string}'`")
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
CheckKind::HardcodedPasswordFuncArg(string) => {
|
||||
format!("Possible hardcoded password: `'{string}'`")
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
CheckKind::HardcodedPasswordDefault(string) => {
|
||||
format!("Possible hardcoded password: `'{string}'`")
|
||||
format!("Possible hardcoded password: `\"{string}\"`")
|
||||
}
|
||||
// Ruff
|
||||
CheckKind::AmbiguousUnicodeCharacterString(confusable, representant) => {
|
||||
@@ -1874,16 +1932,16 @@ impl CheckKind {
|
||||
pub fn summary(&self) -> String {
|
||||
match self {
|
||||
CheckKind::UnaryPrefixIncrement => {
|
||||
"Python does not support the unary prefix increment.".to_string()
|
||||
"Python does not support the unary prefix increment".to_string()
|
||||
}
|
||||
CheckKind::UnusedLoopControlVariable(name) => {
|
||||
format!("Loop control variable `{name}` not used within the loop body.")
|
||||
format!("Loop control variable `{name}` not used within the loop body")
|
||||
}
|
||||
CheckKind::NoAssertRaisesException => {
|
||||
"`assertRaises(Exception):` should be considered evil.".to_string()
|
||||
"`assertRaises(Exception)` should be considered evil".to_string()
|
||||
}
|
||||
CheckKind::StarArgUnpackingAfterKeywordArg => {
|
||||
"Star-arg unpacking after a keyword argument is strongly discouraged.".to_string()
|
||||
"Star-arg unpacking after a keyword argument is strongly discouraged".to_string()
|
||||
}
|
||||
_ => self.body(),
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ pub enum CheckCodePrefix {
|
||||
B01,
|
||||
B010,
|
||||
B011,
|
||||
B012,
|
||||
B013,
|
||||
B014,
|
||||
B015,
|
||||
@@ -55,8 +56,13 @@ pub enum CheckCodePrefix {
|
||||
B018,
|
||||
B019,
|
||||
B02,
|
||||
B020,
|
||||
B021,
|
||||
B022,
|
||||
B024,
|
||||
B025,
|
||||
B026,
|
||||
B027,
|
||||
C,
|
||||
C4,
|
||||
C40,
|
||||
@@ -373,6 +379,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::B009,
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B012,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
@@ -380,8 +387,13 @@ impl CheckCodePrefix {
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B019,
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
],
|
||||
CheckCodePrefix::B0 => vec![
|
||||
CheckCode::B002,
|
||||
@@ -394,6 +406,7 @@ impl CheckCodePrefix {
|
||||
CheckCode::B009,
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B012,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
@@ -401,8 +414,13 @@ impl CheckCodePrefix {
|
||||
CheckCode::B017,
|
||||
CheckCode::B018,
|
||||
CheckCode::B019,
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
],
|
||||
CheckCodePrefix::B00 => vec![
|
||||
CheckCode::B002,
|
||||
@@ -425,6 +443,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B01 => vec![
|
||||
CheckCode::B010,
|
||||
CheckCode::B011,
|
||||
CheckCode::B012,
|
||||
CheckCode::B013,
|
||||
CheckCode::B014,
|
||||
CheckCode::B015,
|
||||
@@ -435,6 +454,7 @@ impl CheckCodePrefix {
|
||||
],
|
||||
CheckCodePrefix::B010 => vec![CheckCode::B010],
|
||||
CheckCodePrefix::B011 => vec![CheckCode::B011],
|
||||
CheckCodePrefix::B012 => vec![CheckCode::B012],
|
||||
CheckCodePrefix::B013 => vec![CheckCode::B013],
|
||||
CheckCodePrefix::B014 => vec![CheckCode::B014],
|
||||
CheckCodePrefix::B015 => vec![CheckCode::B015],
|
||||
@@ -442,9 +462,22 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B017 => vec![CheckCode::B017],
|
||||
CheckCodePrefix::B018 => vec![CheckCode::B018],
|
||||
CheckCodePrefix::B019 => vec![CheckCode::B019],
|
||||
CheckCodePrefix::B02 => vec![CheckCode::B025, CheckCode::B026],
|
||||
CheckCodePrefix::B02 => vec![
|
||||
CheckCode::B020,
|
||||
CheckCode::B021,
|
||||
CheckCode::B022,
|
||||
CheckCode::B024,
|
||||
CheckCode::B025,
|
||||
CheckCode::B026,
|
||||
CheckCode::B027,
|
||||
],
|
||||
CheckCodePrefix::B020 => vec![CheckCode::B020],
|
||||
CheckCodePrefix::B021 => vec![CheckCode::B021],
|
||||
CheckCodePrefix::B022 => vec![CheckCode::B022],
|
||||
CheckCodePrefix::B024 => vec![CheckCode::B024],
|
||||
CheckCodePrefix::B025 => vec![CheckCode::B025],
|
||||
CheckCodePrefix::B026 => vec![CheckCode::B026],
|
||||
CheckCodePrefix::B027 => vec![CheckCode::B027],
|
||||
CheckCodePrefix::C => vec![
|
||||
CheckCode::C400,
|
||||
CheckCode::C401,
|
||||
@@ -1176,6 +1209,7 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B01 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B010 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B011 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B012 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B013 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B014 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B015 => PrefixSpecificity::Explicit,
|
||||
@@ -1184,8 +1218,13 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::B018 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B019 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B02 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::B020 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B021 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B022 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B024 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B025 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B026 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::B027 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::C => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::C4 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::C40 => PrefixSpecificity::Tens,
|
||||
@@ -1338,15 +1377,6 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::I0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::I00 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::I001 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::S1 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::S10 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::S101 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S102 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S104 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S105 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S106 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S107 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::M => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::M0 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::M00 => PrefixSpecificity::Tens,
|
||||
@@ -1383,6 +1413,15 @@ impl CheckCodePrefix {
|
||||
CheckCodePrefix::RUF001 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::RUF002 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::RUF003 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::S1 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::S10 => PrefixSpecificity::Tens,
|
||||
CheckCodePrefix::S101 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S102 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S104 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S105 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S106 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::S107 => PrefixSpecificity::Explicit,
|
||||
CheckCodePrefix::T => PrefixSpecificity::Category,
|
||||
CheckCodePrefix::T2 => PrefixSpecificity::Hundreds,
|
||||
CheckCodePrefix::T20 => PrefixSpecificity::Tens,
|
||||
|
||||
10
src/cli.rs
10
src/cli.rs
@@ -1,8 +1,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{command, Parser};
|
||||
use fnv::FnvHashMap;
|
||||
use log::warn;
|
||||
use regex::Regex;
|
||||
|
||||
@@ -87,6 +87,10 @@ pub struct Cli {
|
||||
/// The minimum Python version that should be supported.
|
||||
#[arg(long)]
|
||||
pub target_version: Option<PythonVersion>,
|
||||
/// Set the line-length for length-associated checks and automatic
|
||||
/// formatting.
|
||||
#[arg(long)]
|
||||
pub line_length: Option<usize>,
|
||||
/// Round-trip auto-formatting.
|
||||
// TODO(charlie): This should be a sub-command.
|
||||
#[arg(long, hide = true)]
|
||||
@@ -120,6 +124,8 @@ pub fn extract_log_level(cli: &Cli) -> LogLevel {
|
||||
LogLevel::Quiet
|
||||
} else if cli.verbose {
|
||||
LogLevel::Verbose
|
||||
} else if matches!(cli.format, SerializationFormat::Json) {
|
||||
LogLevel::Quiet
|
||||
} else {
|
||||
LogLevel::Default
|
||||
}
|
||||
@@ -188,7 +194,7 @@ pub fn collect_per_file_ignores(
|
||||
pairs: Vec<PatternPrefixPair>,
|
||||
project_root: &Option<PathBuf>,
|
||||
) -> Vec<PerFileIgnore> {
|
||||
let mut per_file_ignores: BTreeMap<String, Vec<CheckCodePrefix>> = BTreeMap::new();
|
||||
let mut per_file_ignores: FnvHashMap<String, Vec<CheckCodePrefix>> = FnvHashMap::default();
|
||||
for pair in pairs {
|
||||
per_file_ignores
|
||||
.entry(pair.pattern)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
//! Abstractions for Google-style docstrings.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub(crate) static GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
pub(crate) static GOOGLE_SECTION_NAMES: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"Args",
|
||||
"Arguments",
|
||||
"Attention",
|
||||
@@ -37,35 +36,36 @@ pub(crate) static GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new
|
||||
])
|
||||
});
|
||||
|
||||
pub(crate) static LOWERCASE_GOOGLE_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
"args",
|
||||
"arguments",
|
||||
"attention",
|
||||
"attributes",
|
||||
"caution",
|
||||
"danger",
|
||||
"error",
|
||||
"example",
|
||||
"examples",
|
||||
"hint",
|
||||
"important",
|
||||
"keyword args",
|
||||
"keyword arguments",
|
||||
"methods",
|
||||
"note",
|
||||
"notes",
|
||||
"return",
|
||||
"returns",
|
||||
"raises",
|
||||
"references",
|
||||
"see also",
|
||||
"tip",
|
||||
"todo",
|
||||
"warning",
|
||||
"warnings",
|
||||
"warns",
|
||||
"yield",
|
||||
"yields",
|
||||
])
|
||||
});
|
||||
pub(crate) static LOWERCASE_GOOGLE_SECTION_NAMES: Lazy<FnvHashSet<&'static str>> =
|
||||
Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"args",
|
||||
"arguments",
|
||||
"attention",
|
||||
"attributes",
|
||||
"caution",
|
||||
"danger",
|
||||
"error",
|
||||
"example",
|
||||
"examples",
|
||||
"hint",
|
||||
"important",
|
||||
"keyword args",
|
||||
"keyword arguments",
|
||||
"methods",
|
||||
"note",
|
||||
"notes",
|
||||
"return",
|
||||
"returns",
|
||||
"raises",
|
||||
"references",
|
||||
"see also",
|
||||
"tip",
|
||||
"todo",
|
||||
"warning",
|
||||
"warnings",
|
||||
"warns",
|
||||
"yield",
|
||||
"yields",
|
||||
])
|
||||
});
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
//! Abstractions for NumPy-style docstrings.
|
||||
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"short summary",
|
||||
"extended summary",
|
||||
"parameters",
|
||||
@@ -22,8 +21,8 @@ pub(crate) static LOWERCASE_NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> =
|
||||
])
|
||||
});
|
||||
|
||||
pub(crate) static NUMPY_SECTION_NAMES: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
pub(crate) static NUMPY_SECTION_NAMES: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"Short Summary",
|
||||
"Extended Summary",
|
||||
"Parameters",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::docstrings::google::{GOOGLE_SECTION_NAMES, LOWERCASE_GOOGLE_SECTION_NAMES};
|
||||
@@ -11,14 +10,14 @@ pub(crate) enum SectionStyle {
|
||||
}
|
||||
|
||||
impl SectionStyle {
|
||||
pub(crate) fn section_names(&self) -> &Lazy<BTreeSet<&'static str>> {
|
||||
pub(crate) fn section_names(&self) -> &Lazy<FnvHashSet<&'static str>> {
|
||||
match self {
|
||||
SectionStyle::NumPy => &NUMPY_SECTION_NAMES,
|
||||
SectionStyle::Google => &GOOGLE_SECTION_NAMES,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lowercase_section_names(&self) -> &Lazy<BTreeSet<&'static str>> {
|
||||
pub(crate) fn lowercase_section_names(&self) -> &Lazy<FnvHashSet<&'static str>> {
|
||||
match self {
|
||||
SectionStyle::NumPy => &LOWERCASE_NUMPY_SECTION_NAMES,
|
||||
SectionStyle::Google => &LOWERCASE_GOOGLE_SECTION_NAMES,
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
use num_bigint::BigInt;
|
||||
use rustpython_ast::{Cmpop, Constant, Expr, ExprKind, Located};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr_from_module;
|
||||
use crate::ast::helpers::match_module_member;
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
fn is_sys(checker: &Checker, expr: &Expr, target: &str) -> bool {
|
||||
match_name_or_attr_from_module(expr, target, "sys", checker.from_imports.get("sys"))
|
||||
match_module_member(
|
||||
expr,
|
||||
"sys",
|
||||
target,
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
)
|
||||
}
|
||||
|
||||
/// YTT101, YTT102, YTT301, YTT303
|
||||
@@ -181,9 +187,13 @@ pub fn compare(checker: &mut Checker, left: &Expr, ops: &[Cmpop], comparators: &
|
||||
|
||||
/// YTT202
|
||||
pub fn name_or_attribute(checker: &mut Checker, expr: &Expr) {
|
||||
if match_name_or_attr_from_module(expr, "PY3", "six", checker.from_imports.get("six"))
|
||||
&& checker.settings.enabled.contains(&CheckCode::YTT202)
|
||||
{
|
||||
if match_module_member(
|
||||
expr,
|
||||
"six",
|
||||
"PY3",
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::SixPY3Referenced,
|
||||
Range::from_located(expr),
|
||||
|
||||
@@ -49,10 +49,13 @@ fn is_none_returning(body: &[Stmt]) -> bool {
|
||||
}
|
||||
|
||||
/// ANN401
|
||||
fn check_dynamically_typed(checker: &mut Checker, annotation: &Expr, name: &str) {
|
||||
if checker.match_typing_module(annotation, "Any") {
|
||||
fn check_dynamically_typed<F>(checker: &mut Checker, annotation: &Expr, func: F)
|
||||
where
|
||||
F: FnOnce() -> String,
|
||||
{
|
||||
if checker.match_typing_expr(annotation, "Any") {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DynamicallyTypedExpression(name.to_string()),
|
||||
CheckKind::DynamicallyTypedExpression(func()),
|
||||
Range::from_located(annotation),
|
||||
));
|
||||
};
|
||||
@@ -100,7 +103,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
{
|
||||
if let Some(expr) = &arg.node.annotation {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
check_dynamically_typed(checker, expr, &arg.node.arg);
|
||||
check_dynamically_typed(checker, expr, || arg.node.arg.to_string());
|
||||
};
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
@@ -122,7 +125,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
let name = arg.node.arg.to_string();
|
||||
check_dynamically_typed(checker, expr, &format!("*{name}"));
|
||||
check_dynamically_typed(checker, expr, || format!("*{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -145,7 +148,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
let name = arg.node.arg.to_string();
|
||||
check_dynamically_typed(checker, expr, &format!("**{name}"));
|
||||
check_dynamically_typed(checker, expr, || format!("**{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -165,7 +168,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
// ANN201, ANN202, ANN401
|
||||
if let Some(expr) = &returns {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
check_dynamically_typed(checker, expr, name);
|
||||
check_dynamically_typed(checker, expr, || name.to_string());
|
||||
};
|
||||
} else {
|
||||
// Allow omission of return annotation in `__init__` functions, if the function
|
||||
@@ -215,7 +218,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if let Some(annotation) = &arg.node.annotation {
|
||||
has_any_typed_arg = true;
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
check_dynamically_typed(checker, annotation, &arg.node.arg);
|
||||
check_dynamically_typed(checker, annotation, || arg.node.arg.to_string());
|
||||
}
|
||||
} else {
|
||||
if !(checker.settings.flake8_annotations.suppress_dummy_args
|
||||
@@ -238,7 +241,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
let name = arg.node.arg.to_string();
|
||||
check_dynamically_typed(checker, expr, &format!("*{name}"));
|
||||
check_dynamically_typed(checker, expr, || format!("*{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -262,7 +265,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
if !checker.settings.flake8_annotations.allow_star_arg_any {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
let name = arg.node.arg.to_string();
|
||||
check_dynamically_typed(checker, expr, &format!("**{name}"));
|
||||
check_dynamically_typed(checker, expr, || format!("**{name}"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -305,7 +308,7 @@ pub fn definition(checker: &mut Checker, definition: &Definition, visibility: &V
|
||||
// ANN201, ANN202
|
||||
if let Some(expr) = &returns {
|
||||
if checker.settings.enabled.contains(&CheckCode::ANN401) {
|
||||
check_dynamically_typed(checker, expr, name);
|
||||
check_dynamically_typed(checker, expr, || name.to_string());
|
||||
}
|
||||
} else {
|
||||
// Allow omission of return annotation in `__init__` functions, if the function
|
||||
|
||||
129
src/flake8_bugbear/plugins/abstract_base_class.rs
Normal file
129
src/flake8_bugbear/plugins/abstract_base_class.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Keyword, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::match_module_member;
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckCode, CheckKind};
|
||||
|
||||
fn is_abc_class(
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
keywords.iter().any(|keyword| {
|
||||
keyword
|
||||
.node
|
||||
.arg
|
||||
.as_ref()
|
||||
.map(|a| a == "metaclass")
|
||||
.unwrap_or(false)
|
||||
&& match_module_member(
|
||||
&keyword.node.value,
|
||||
"abc",
|
||||
"ABCMeta",
|
||||
from_imports,
|
||||
import_aliases,
|
||||
)
|
||||
}) || bases
|
||||
.iter()
|
||||
.any(|base| match_module_member(base, "abc", "ABC", from_imports, import_aliases))
|
||||
}
|
||||
|
||||
fn is_empty_body(body: &[Stmt]) -> bool {
|
||||
body.iter().all(|stmt| match &stmt.node {
|
||||
StmtKind::Pass => true,
|
||||
StmtKind::Expr { value } => match &value.node {
|
||||
ExprKind::Constant { value, .. } => {
|
||||
matches!(value, Constant::Str(..) | Constant::Ellipsis)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_abstractmethod(
|
||||
expr: &Expr,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
match_module_member(expr, "abc", "abstractmethod", from_imports, import_aliases)
|
||||
}
|
||||
|
||||
fn is_overload(
|
||||
expr: &Expr,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
match_module_member(expr, "typing", "overload", from_imports, import_aliases)
|
||||
}
|
||||
|
||||
pub fn abstract_base_class(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
bases: &[Expr],
|
||||
keywords: &[Keyword],
|
||||
body: &[Stmt],
|
||||
) {
|
||||
if bases.len() + keywords.len() == 1
|
||||
&& is_abc_class(
|
||||
bases,
|
||||
keywords,
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
)
|
||||
{
|
||||
let mut has_abstract_method = false;
|
||||
for stmt in body {
|
||||
// https://github.com/PyCQA/flake8-bugbear/issues/293
|
||||
// Ignore abc's that declares a class attribute that must be set
|
||||
if let StmtKind::AnnAssign { .. } | StmtKind::Assign { .. } = &stmt.node {
|
||||
has_abstract_method = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if let StmtKind::FunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
..
|
||||
}
|
||||
| StmtKind::AsyncFunctionDef {
|
||||
decorator_list,
|
||||
body,
|
||||
..
|
||||
} = &stmt.node
|
||||
{
|
||||
let has_abstract_decorator = decorator_list
|
||||
.iter()
|
||||
.any(|d| is_abstractmethod(d, &checker.from_imports, &checker.import_aliases));
|
||||
|
||||
has_abstract_method |= has_abstract_decorator;
|
||||
|
||||
if checker.settings.enabled.contains(&CheckCode::B027) {
|
||||
if !has_abstract_decorator
|
||||
&& is_empty_body(body)
|
||||
&& !decorator_list
|
||||
.iter()
|
||||
.any(|d| is_overload(d, &checker.from_imports, &checker.import_aliases))
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::EmptyMethodWithoutAbstractDecorator(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if checker.settings.enabled.contains(&CheckCode::B024) {
|
||||
if !has_abstract_method {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::AbstractBaseClassWithoutAbstractMethod(name.to_string()),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,10 @@ pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[With
|
||||
if let Some(item) = items.first() {
|
||||
let item_context = &item.context_expr;
|
||||
if let ExprKind::Call { func, args, .. } = &item_context.node {
|
||||
if match_name_or_attr(func, "assertRaises")
|
||||
&& args.len() == 1
|
||||
&& match_name_or_attr(args.first().unwrap(), "Exception")
|
||||
if args.len() == 1
|
||||
&& item.optional_vars.is_none()
|
||||
&& match_name_or_attr(func, "assertRaises")
|
||||
&& match_name_or_attr(args.first().unwrap(), "Exception")
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NoAssertRaisesException,
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::helpers::{compose_call_path, match_name_or_attr_from_module};
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
|
||||
use crate::ast::types::{Range, ScopeKind};
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
fn is_cache_func(checker: &Checker, expr: &Expr) -> bool {
|
||||
match_name_or_attr_from_module(
|
||||
expr,
|
||||
"lru_cache",
|
||||
"functools",
|
||||
checker.from_imports.get("functools"),
|
||||
) || match_name_or_attr_from_module(
|
||||
expr,
|
||||
"cache",
|
||||
"functools",
|
||||
checker.from_imports.get("functools"),
|
||||
)
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), &checker.import_aliases);
|
||||
match_call_path(&call_path, "functools", "lru_cache", &checker.from_imports)
|
||||
|| match_call_path(&call_path, "functools", "cache", &checker.from_imports)
|
||||
}
|
||||
|
||||
/// B019
|
||||
@@ -25,8 +17,8 @@ pub fn cached_instance_method(checker: &mut Checker, decorator_list: &[Expr]) {
|
||||
for decorator in decorator_list {
|
||||
// TODO(charlie): This should take into account `classmethod-decorators` and
|
||||
// `staticmethod-decorators`.
|
||||
if let Some(decorator_path) = compose_call_path(decorator) {
|
||||
if decorator_path == "classmethod" || decorator_path == "staticmethod" {
|
||||
if let ExprKind::Name { id, .. } = &decorator.node {
|
||||
if id == "classmethod" || id == "staticmethod" {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,20 +21,21 @@ fn type_pattern(elts: Vec<&Expr>) -> Expr {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn duplicate_handler_exceptions(
|
||||
fn duplicate_handler_exceptions<'a>(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
elts: &[Expr],
|
||||
) -> BTreeSet<String> {
|
||||
let mut seen: BTreeSet<String> = Default::default();
|
||||
let mut duplicates: BTreeSet<String> = Default::default();
|
||||
expr: &'a Expr,
|
||||
elts: &'a [Expr],
|
||||
) -> BTreeSet<Vec<&'a str>> {
|
||||
let mut seen: BTreeSet<Vec<&str>> = Default::default();
|
||||
let mut duplicates: BTreeSet<Vec<&str>> = Default::default();
|
||||
let mut unique_elts: Vec<&Expr> = Default::default();
|
||||
for type_ in elts {
|
||||
if let Some(name) = helpers::compose_call_path(type_) {
|
||||
if seen.contains(&name) {
|
||||
duplicates.insert(name);
|
||||
let call_path = helpers::collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
if seen.contains(&call_path) {
|
||||
duplicates.insert(call_path);
|
||||
} else {
|
||||
seen.insert(name);
|
||||
seen.insert(call_path);
|
||||
unique_elts.push(type_);
|
||||
}
|
||||
}
|
||||
@@ -45,7 +46,11 @@ pub fn duplicate_handler_exceptions(
|
||||
if !duplicates.is_empty() {
|
||||
let mut check = Check::new(
|
||||
CheckKind::DuplicateHandlerException(
|
||||
duplicates.into_iter().sorted().collect::<Vec<String>>(),
|
||||
duplicates
|
||||
.into_iter()
|
||||
.map(|call_path| call_path.join("."))
|
||||
.sorted()
|
||||
.collect::<Vec<String>>(),
|
||||
),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
@@ -70,19 +75,20 @@ pub fn duplicate_handler_exceptions(
|
||||
}
|
||||
|
||||
pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Excepthandler]) {
|
||||
let mut seen: BTreeSet<String> = Default::default();
|
||||
let mut duplicates: BTreeSet<String> = Default::default();
|
||||
let mut seen: BTreeSet<Vec<&str>> = Default::default();
|
||||
let mut duplicates: BTreeSet<Vec<&str>> = Default::default();
|
||||
for handler in handlers {
|
||||
match &handler.node {
|
||||
ExcepthandlerKind::ExceptHandler { type_, .. } => {
|
||||
if let Some(type_) = type_ {
|
||||
match &type_.node {
|
||||
ExprKind::Attribute { .. } | ExprKind::Name { .. } => {
|
||||
if let Some(name) = helpers::compose_call_path(type_) {
|
||||
if seen.contains(&name) {
|
||||
duplicates.insert(name);
|
||||
let call_path = helpers::collect_call_paths(type_);
|
||||
if !call_path.is_empty() {
|
||||
if seen.contains(&call_path) {
|
||||
duplicates.insert(call_path);
|
||||
} else {
|
||||
seen.insert(name);
|
||||
seen.insert(call_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,7 +111,7 @@ pub fn duplicate_exceptions(checker: &mut Checker, stmt: &Stmt, handlers: &[Exce
|
||||
if checker.settings.enabled.contains(&CheckCode::B025) {
|
||||
for duplicate in duplicates.into_iter().sorted() {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::DuplicateTryBlockException(duplicate),
|
||||
CheckKind::DuplicateTryBlockException(duplicate.join(".")),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
|
||||
19
src/flake8_bugbear/plugins/f_string_docstring.rs
Normal file
19
src/flake8_bugbear/plugins/f_string_docstring.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use rustpython_ast::{ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// B021
|
||||
pub fn f_string_docstring(checker: &mut Checker, body: &[Stmt]) {
|
||||
if let Some(stmt) = body.first() {
|
||||
if let StmtKind::Expr { value } = &stmt.node {
|
||||
if let ExprKind::JoinedStr { .. } = value.node {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::FStringDocstring,
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use rustpython_ast::{Arguments, Constant, Expr, ExprKind};
|
||||
|
||||
use crate::ast::helpers::compose_call_path;
|
||||
use crate::ast::helpers::{
|
||||
collect_call_paths, compose_call_path, dealias_call_path, match_call_path,
|
||||
};
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::visitor;
|
||||
use crate::ast::visitor::Visitor;
|
||||
@@ -9,58 +11,34 @@ use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::flake8_bugbear::plugins::mutable_argument_default::is_mutable_func;
|
||||
|
||||
const IMMUTABLE_FUNCS: [&str; 7] = [
|
||||
"tuple",
|
||||
"frozenset",
|
||||
"operator.attrgetter",
|
||||
"operator.itemgetter",
|
||||
"operator.methodcaller",
|
||||
"types.MappingProxyType",
|
||||
"re.compile",
|
||||
const IMMUTABLE_FUNCS: [(&str, &str); 7] = [
|
||||
("", "tuple"),
|
||||
("", "frozenset"),
|
||||
("operator", "attrgetter"),
|
||||
("operator", "itemgetter"),
|
||||
("operator", "methodcaller"),
|
||||
("types", "MappingProxyType"),
|
||||
("re", "compile"),
|
||||
];
|
||||
|
||||
fn is_immutable_func(
|
||||
expr: &Expr,
|
||||
extend_immutable_calls: &[&str],
|
||||
extend_immutable_calls: &[(&str, &str)],
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
compose_call_path(expr).map_or_else(
|
||||
|| false,
|
||||
|call_path| {
|
||||
// It matches the call path exactly (`operator.methodcaller`).
|
||||
for target in IMMUTABLE_FUNCS.iter().chain(extend_immutable_calls) {
|
||||
if &call_path == target {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// It matches the member name, and was imported from that module (`methodcaller`
|
||||
// following `from operator import methodcaller`).
|
||||
if !call_path.contains('.') {
|
||||
for target in IMMUTABLE_FUNCS.iter().chain(extend_immutable_calls) {
|
||||
let mut splitter = target.rsplit('.');
|
||||
if let (Some(member), Some(module)) = (splitter.next(), splitter.next()) {
|
||||
if call_path == member
|
||||
&& from_imports
|
||||
.get(module)
|
||||
.map(|module| module.contains(member))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
},
|
||||
)
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
IMMUTABLE_FUNCS
|
||||
.iter()
|
||||
.chain(extend_immutable_calls)
|
||||
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
|
||||
}
|
||||
|
||||
struct ArgumentDefaultVisitor<'a> {
|
||||
checks: Vec<(CheckKind, Range)>,
|
||||
extend_immutable_calls: &'a [&'a str],
|
||||
extend_immutable_calls: &'a [(&'a str, &'a str)],
|
||||
from_imports: &'a FnvHashMap<&'a str, FnvHashSet<&'a str>>,
|
||||
import_aliases: &'a FnvHashMap<&'a str, &'a str>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for ArgumentDefaultVisitor<'b>
|
||||
@@ -70,12 +48,17 @@ where
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
match &expr.node {
|
||||
ExprKind::Call { func, args, .. } => {
|
||||
if !is_mutable_func(func, self.from_imports)
|
||||
&& !is_immutable_func(func, self.extend_immutable_calls, self.from_imports)
|
||||
if !is_mutable_func(func, self.from_imports, self.import_aliases)
|
||||
&& !is_immutable_func(
|
||||
func,
|
||||
self.extend_immutable_calls,
|
||||
self.from_imports,
|
||||
self.import_aliases,
|
||||
)
|
||||
&& !is_nan_or_infinity(func, args)
|
||||
{
|
||||
self.checks.push((
|
||||
CheckKind::FunctionCallArgumentDefault,
|
||||
CheckKind::FunctionCallArgumentDefault(compose_call_path(expr)),
|
||||
Range::from_located(expr),
|
||||
))
|
||||
}
|
||||
@@ -115,17 +98,26 @@ fn is_nan_or_infinity(expr: &Expr, args: &[Expr]) -> bool {
|
||||
|
||||
/// B008
|
||||
pub fn function_call_argument_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
let extend_immutable_cells: Vec<&str> = checker
|
||||
// Map immutable calls to (module, member) format.
|
||||
let extend_immutable_cells: Vec<(&str, &str)> = checker
|
||||
.settings
|
||||
.flake8_bugbear
|
||||
.extend_immutable_calls
|
||||
.iter()
|
||||
.map(|s| s.as_str())
|
||||
.map(|s| {
|
||||
let s = s.as_str();
|
||||
if let Some(index) = s.rfind('.') {
|
||||
(&s[..index], &s[index + 1..])
|
||||
} else {
|
||||
("", s)
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut visitor = ArgumentDefaultVisitor {
|
||||
checks: vec![],
|
||||
extend_immutable_calls: &extend_immutable_cells,
|
||||
from_imports: &checker.from_imports,
|
||||
import_aliases: &checker.import_aliases,
|
||||
};
|
||||
for expr in arguments
|
||||
.defaults
|
||||
|
||||
49
src/flake8_bugbear/plugins/jump_statement_in_finally.rs
Normal file
49
src/flake8_bugbear/plugins/jump_statement_in_finally.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
use rustpython_ast::{Stmt, StmtKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
fn walk_stmt(checker: &mut Checker, body: &[Stmt], f: fn(&Stmt) -> bool) {
|
||||
for stmt in body {
|
||||
if f(stmt) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::JumpStatementInFinally(match &stmt.node {
|
||||
StmtKind::Break { .. } => "break".to_string(),
|
||||
StmtKind::Continue { .. } => "continue".to_string(),
|
||||
StmtKind::Return { .. } => "return".to_string(),
|
||||
_ => unreachable!(
|
||||
"Expected StmtKind::Break | StmtKind::Continue | StmtKind::Return"
|
||||
),
|
||||
}),
|
||||
Range::from_located(stmt),
|
||||
));
|
||||
}
|
||||
match &stmt.node {
|
||||
StmtKind::While { body, .. }
|
||||
| StmtKind::For { body, .. }
|
||||
| StmtKind::AsyncFor { body, .. } => {
|
||||
walk_stmt(checker, body, |stmt| {
|
||||
matches!(stmt.node, StmtKind::Return { .. })
|
||||
});
|
||||
}
|
||||
StmtKind::If { body, .. }
|
||||
| StmtKind::Try { body, .. }
|
||||
| StmtKind::With { body, .. }
|
||||
| StmtKind::AsyncWith { body, .. } => {
|
||||
walk_stmt(checker, body, f);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// B012
|
||||
pub fn jump_statement_in_finally(checker: &mut Checker, finalbody: &[Stmt]) {
|
||||
walk_stmt(checker, finalbody, |stmt| {
|
||||
matches!(
|
||||
stmt.node,
|
||||
StmtKind::Break | StmtKind::Continue | StmtKind::Return { .. }
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use fnv::FnvHashMap;
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::ast::visitor;
|
||||
use crate::ast::visitor::Visitor;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
#[derive(Default)]
|
||||
struct NameFinder<'a> {
|
||||
names: FnvHashMap<&'a str, &'a Expr>,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Visitor<'b> for NameFinder<'a>
|
||||
where
|
||||
'b: 'a,
|
||||
{
|
||||
fn visit_expr(&mut self, expr: &'b Expr) {
|
||||
match &expr.node {
|
||||
ExprKind::Name { id, .. } => {
|
||||
self.names.insert(id, expr);
|
||||
}
|
||||
ExprKind::ListComp { generators, .. }
|
||||
| ExprKind::DictComp { generators, .. }
|
||||
| ExprKind::SetComp { generators, .. }
|
||||
| ExprKind::GeneratorExp { generators, .. } => {
|
||||
for comp in generators {
|
||||
self.visit_expr(&comp.iter);
|
||||
}
|
||||
}
|
||||
ExprKind::Lambda { args, body } => {
|
||||
visitor::walk_expr(self, body);
|
||||
for arg in args.args.iter() {
|
||||
self.names.remove(arg.node.arg.as_str());
|
||||
}
|
||||
}
|
||||
_ => visitor::walk_expr(self, expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// B020
|
||||
pub fn loop_variable_overrides_iterator(checker: &mut Checker, target: &Expr, iter: &Expr) {
|
||||
let target_names = {
|
||||
let mut target_finder = NameFinder::default();
|
||||
target_finder.visit_expr(target);
|
||||
target_finder.names
|
||||
};
|
||||
let iter_names = {
|
||||
let mut iter_finder = NameFinder::default();
|
||||
iter_finder.visit_expr(iter);
|
||||
iter_finder.names
|
||||
};
|
||||
|
||||
for (name, expr) in target_names {
|
||||
if iter_names.contains_key(name) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::LoopVariableOverridesIterator(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
pub use abstract_base_class::abstract_base_class;
|
||||
pub use assert_false::assert_false;
|
||||
pub use assert_raises_exception::assert_raises_exception;
|
||||
pub use assignment_to_os_environ::assignment_to_os_environ;
|
||||
pub use cached_instance_method::cached_instance_method;
|
||||
pub use cannot_raise_literal::cannot_raise_literal;
|
||||
pub use duplicate_exceptions::{duplicate_exceptions, duplicate_handler_exceptions};
|
||||
pub use duplicate_exceptions::duplicate_exceptions;
|
||||
pub use f_string_docstring::f_string_docstring;
|
||||
pub use function_call_argument_default::function_call_argument_default;
|
||||
pub use getattr_with_constant::getattr_with_constant;
|
||||
pub use jump_statement_in_finally::jump_statement_in_finally;
|
||||
pub use loop_variable_overrides_iterator::loop_variable_overrides_iterator;
|
||||
pub use mutable_argument_default::mutable_argument_default;
|
||||
pub use redundant_tuple_in_exception_handler::redundant_tuple_in_exception_handler;
|
||||
pub use setattr_with_constant::setattr_with_constant;
|
||||
@@ -15,16 +19,21 @@ pub use unary_prefix_increment::unary_prefix_increment;
|
||||
pub use unreliable_callable_check::unreliable_callable_check;
|
||||
pub use unused_loop_control_variable::unused_loop_control_variable;
|
||||
pub use useless_comparison::useless_comparison;
|
||||
pub use useless_contextlib_suppress::useless_contextlib_suppress;
|
||||
pub use useless_expression::useless_expression;
|
||||
|
||||
mod abstract_base_class;
|
||||
mod assert_false;
|
||||
mod assert_raises_exception;
|
||||
mod assignment_to_os_environ;
|
||||
mod cached_instance_method;
|
||||
mod cannot_raise_literal;
|
||||
mod duplicate_exceptions;
|
||||
mod f_string_docstring;
|
||||
mod function_call_argument_default;
|
||||
mod getattr_with_constant;
|
||||
mod jump_statement_in_finally;
|
||||
mod loop_variable_overrides_iterator;
|
||||
mod mutable_argument_default;
|
||||
mod redundant_tuple_in_exception_handler;
|
||||
mod setattr_with_constant;
|
||||
@@ -34,4 +43,5 @@ mod unary_prefix_increment;
|
||||
mod unreliable_callable_check;
|
||||
mod unused_loop_control_variable;
|
||||
mod useless_comparison;
|
||||
mod useless_contextlib_suppress;
|
||||
mod useless_expression;
|
||||
|
||||
@@ -1,53 +1,30 @@
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use rustpython_ast::{Arguments, Expr, ExprKind};
|
||||
|
||||
use crate::ast::helpers::compose_call_path;
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
const MUTABLE_FUNCS: [&str; 7] = [
|
||||
"dict",
|
||||
"list",
|
||||
"set",
|
||||
"collections.Counter",
|
||||
"collections.OrderedDict",
|
||||
"collections.defaultdict",
|
||||
"collections.deque",
|
||||
const MUTABLE_FUNCS: [(&str, &str); 7] = [
|
||||
("", "dict"),
|
||||
("", "list"),
|
||||
("", "set"),
|
||||
("collections", "Counter"),
|
||||
("collections", "OrderedDict"),
|
||||
("collections", "defaultdict"),
|
||||
("collections", "deque"),
|
||||
];
|
||||
|
||||
pub fn is_mutable_func(expr: &Expr, from_imports: &FnvHashMap<&str, FnvHashSet<&str>>) -> bool {
|
||||
compose_call_path(expr).map_or_else(
|
||||
|| false,
|
||||
|call_path| {
|
||||
// It matches the call path exactly (`collections.Counter`).
|
||||
for target in MUTABLE_FUNCS {
|
||||
if call_path == target {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// It matches the member name, and was imported from that module (`Counter`
|
||||
// following `from collections import Counter`).
|
||||
if !call_path.contains('.') {
|
||||
for target in MUTABLE_FUNCS {
|
||||
let mut splitter = target.rsplit('.');
|
||||
if let (Some(member), Some(module)) = (splitter.next(), splitter.next()) {
|
||||
if call_path == member
|
||||
&& from_imports
|
||||
.get(module)
|
||||
.map(|module| module.contains(member))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
},
|
||||
)
|
||||
pub fn is_mutable_func(
|
||||
expr: &Expr,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
MUTABLE_FUNCS
|
||||
.iter()
|
||||
.any(|(module, member)| match_call_path(&call_path, module, member, from_imports))
|
||||
}
|
||||
|
||||
/// B006
|
||||
@@ -70,7 +47,7 @@ pub fn mutable_argument_default(checker: &mut Checker, arguments: &Arguments) {
|
||||
));
|
||||
}
|
||||
ExprKind::Call { func, .. } => {
|
||||
if is_mutable_func(func, &checker.from_imports) {
|
||||
if is_mutable_func(func, &checker.from_imports, &checker.import_aliases) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MutableArgumentDefault,
|
||||
Range::from_located(expr),
|
||||
|
||||
22
src/flake8_bugbear/plugins/useless_contextlib_suppress.rs
Normal file
22
src/flake8_bugbear/plugins/useless_contextlib_suppress.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, match_call_path};
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// B005
|
||||
pub fn useless_contextlib_suppress(checker: &mut Checker, expr: &Expr, args: &[Expr]) {
|
||||
if match_call_path(
|
||||
&collect_call_paths(expr),
|
||||
"contextlib",
|
||||
"suppress",
|
||||
&checker.from_imports,
|
||||
) && args.is_empty()
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::UselessContextlibSuppress,
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
source: src/flake8_bugbear/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: Depends
|
||||
location:
|
||||
row: 19
|
||||
column: 50
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read};
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use fnv::FnvHashSet;
|
||||
use log::debug;
|
||||
use path_absolutize::{path_dedot, Absolutize};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
@@ -121,7 +121,7 @@ pub fn iter_python_files<'a>(
|
||||
pub(crate) fn ignores_from_path<'a>(
|
||||
path: &Path,
|
||||
pattern_code_pairs: &'a [PerFileIgnore],
|
||||
) -> Result<BTreeSet<&'a CheckCode>> {
|
||||
) -> Result<FnvHashSet<&'a CheckCode>> {
|
||||
let (file_path, file_basename) = extract_path_names(path)?;
|
||||
Ok(pattern_code_pairs
|
||||
.iter()
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::python::sys::KNOWN_STANDARD_LIBRARY;
|
||||
|
||||
#[derive(Debug, PartialOrd, Ord, PartialEq, Eq, Clone)]
|
||||
@@ -31,8 +29,8 @@ pub fn categorize(
|
||||
ImportType::ThirdParty
|
||||
} else if extra_standard_library.contains(module_base) {
|
||||
ImportType::StandardLibrary
|
||||
} else if let Some(import_type) = STATIC_CLASSIFICATIONS.get(module_base) {
|
||||
import_type.clone()
|
||||
} else if module_base == "__future__" {
|
||||
ImportType::Future
|
||||
} else if KNOWN_STANDARD_LIBRARY.contains(module_base) {
|
||||
ImportType::StandardLibrary
|
||||
} else if find_local(src, module_base) {
|
||||
@@ -42,14 +40,6 @@ pub fn categorize(
|
||||
}
|
||||
}
|
||||
|
||||
static STATIC_CLASSIFICATIONS: Lazy<BTreeMap<&'static str, ImportType>> = Lazy::new(|| {
|
||||
BTreeMap::from([
|
||||
("__future__", ImportType::Future),
|
||||
// Relative imports (e.g., `from . import module`).
|
||||
("", ImportType::FirstParty),
|
||||
])
|
||||
});
|
||||
|
||||
fn find_local(paths: &[PathBuf], base: &str) -> bool {
|
||||
for path in paths {
|
||||
if let Ok(metadata) = fs::metadata(path.join(base)) {
|
||||
|
||||
106
src/isort/mod.rs
106
src/isort/mod.rs
@@ -1,6 +1,7 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use itertools::Itertools;
|
||||
use ropey::RopeBuilder;
|
||||
use rustpython_ast::{Stmt, StmtKind};
|
||||
@@ -36,15 +37,25 @@ fn normalize_imports<'a>(imports: &'a [&'a Stmt]) -> ImportBlock<'a> {
|
||||
names,
|
||||
level,
|
||||
} => {
|
||||
let targets = block
|
||||
.import_from
|
||||
.entry(ImportFromData { module, level })
|
||||
.or_default();
|
||||
for name in names {
|
||||
targets.insert(AliasData {
|
||||
name: &name.node.name,
|
||||
asname: &name.node.asname,
|
||||
});
|
||||
if name.node.asname.is_none() {
|
||||
block
|
||||
.import_from
|
||||
.entry(ImportFromData { module, level })
|
||||
.or_default()
|
||||
.insert(AliasData {
|
||||
name: &name.node.name,
|
||||
asname: &name.node.asname,
|
||||
});
|
||||
} else {
|
||||
block.import_from_as.insert((
|
||||
ImportFromData { module, level },
|
||||
AliasData {
|
||||
name: &name.node.name,
|
||||
asname: &name.node.asname,
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => unreachable!("Expected StmtKind::Import | StmtKind::ImportFrom"),
|
||||
@@ -77,7 +88,7 @@ fn categorize_imports<'a>(
|
||||
.import
|
||||
.insert(alias);
|
||||
}
|
||||
// Categorize `StmtKind::ImportFrom`.
|
||||
// Categorize `StmtKind::ImportFrom` (without re-export).
|
||||
for (import_from, aliases) in block.import_from {
|
||||
let classification = categorize(
|
||||
&import_from.module_base(),
|
||||
@@ -93,36 +104,74 @@ fn categorize_imports<'a>(
|
||||
.import_from
|
||||
.insert(import_from, aliases);
|
||||
}
|
||||
// Categorize `StmtKind::ImportFrom` (with re-export).
|
||||
for (import_from, alias) in block.import_from_as {
|
||||
let classification = categorize(
|
||||
&import_from.module_base(),
|
||||
import_from.level,
|
||||
src,
|
||||
known_first_party,
|
||||
known_third_party,
|
||||
extra_standard_library,
|
||||
);
|
||||
block_by_type
|
||||
.entry(classification)
|
||||
.or_default()
|
||||
.import_from_as
|
||||
.insert((import_from, alias));
|
||||
}
|
||||
block_by_type
|
||||
}
|
||||
|
||||
fn sort_imports(block: ImportBlock) -> OrderedImportBlock {
|
||||
let mut ordered: OrderedImportBlock = Default::default();
|
||||
|
||||
// Sort `StmtKind::Import`.
|
||||
for import in block
|
||||
.import
|
||||
.into_iter()
|
||||
.sorted_by_cached_key(|alias| module_key(alias.name))
|
||||
{
|
||||
ordered.import.push(import);
|
||||
}
|
||||
ordered.import.extend(
|
||||
block
|
||||
.import
|
||||
.into_iter()
|
||||
.sorted_by_cached_key(|alias| module_key(alias.name, alias.asname)),
|
||||
);
|
||||
|
||||
// Sort `StmtKind::ImportFrom`.
|
||||
for (import_from, aliases) in
|
||||
ordered.import_from.extend(
|
||||
// Include all non-re-exports.
|
||||
block
|
||||
.import_from
|
||||
.into_iter()
|
||||
.sorted_by_cached_key(|(import_from, _)| {
|
||||
import_from.module.as_ref().map(|module| module_key(module))
|
||||
.chain(
|
||||
// Include all re-exports.
|
||||
block
|
||||
.import_from_as
|
||||
.into_iter()
|
||||
.map(|(import_from, alias)| (import_from, FnvHashSet::from_iter([alias]))),
|
||||
)
|
||||
.map(|(import_from, aliases)| {
|
||||
// Within each `StmtKind::ImportFrom`, sort the members.
|
||||
(
|
||||
import_from,
|
||||
aliases
|
||||
.into_iter()
|
||||
.sorted_by_cached_key(|alias| member_key(alias.name, alias.asname))
|
||||
.collect::<Vec<AliasData>>(),
|
||||
)
|
||||
})
|
||||
{
|
||||
ordered.import_from.push((
|
||||
import_from,
|
||||
aliases
|
||||
.into_iter()
|
||||
.sorted_by_cached_key(|alias| member_key(alias.name))
|
||||
.collect(),
|
||||
));
|
||||
}
|
||||
.sorted_by_cached_key(|(import_from, aliases)| {
|
||||
// Sort each `StmtKind::ImportFrom` by module key, breaking ties based on
|
||||
// members.
|
||||
(
|
||||
import_from
|
||||
.module
|
||||
.as_ref()
|
||||
.map(|module| module_key(module, &None)),
|
||||
aliases
|
||||
.first()
|
||||
.map(|alias| member_key(alias.name, alias.asname)),
|
||||
)
|
||||
}),
|
||||
);
|
||||
|
||||
ordered
|
||||
}
|
||||
|
||||
@@ -252,6 +301,7 @@ mod tests {
|
||||
#[test_case(Path::new("separate_local_folder_imports.py"))]
|
||||
#[test_case(Path::new("separate_third_party_imports.py"))]
|
||||
#[test_case(Path::new("skip.py"))]
|
||||
#[test_case(Path::new("sort_similar_imports.py"))]
|
||||
#[test_case(Path::new("trailing_suffix.py"))]
|
||||
fn isort(path: &Path) -> Result<()> {
|
||||
let snapshot = format!("{}", path.to_string_lossy());
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
source: src/isort/mod.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: UnsortedImports
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 27
|
||||
column: 0
|
||||
fix:
|
||||
patch:
|
||||
content: "import A\nimport a\nimport B\nimport b\nimport x\nimport x as A\nimport x as Y\nimport x as a\nimport x as y\nfrom a import BAD as DEF\nfrom a import B, b\nfrom a import B as A\nfrom a import B as Abc\nfrom a import B as DEF\nfrom a import Boo as DEF\nfrom a import b as a\nfrom a import b as c\nfrom a import b as d\nfrom a import b as x\nfrom a import b as y\nfrom b import C, c\nfrom b import c as d\n"
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 27
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
@@ -8,16 +8,22 @@ pub enum Prefix {
|
||||
Variables,
|
||||
}
|
||||
|
||||
pub fn module_key(module_name: &str) -> String {
|
||||
module_name.to_lowercase()
|
||||
pub fn module_key<'a>(
|
||||
name: &'a str,
|
||||
asname: &'a Option<String>,
|
||||
) -> (String, &'a str, &'a Option<String>) {
|
||||
(name.to_lowercase(), name, asname)
|
||||
}
|
||||
|
||||
pub fn member_key(member_name: &str) -> (Prefix, String) {
|
||||
pub fn member_key<'a>(
|
||||
name: &'a str,
|
||||
asname: &'a Option<String>,
|
||||
) -> (Prefix, String, &'a Option<String>) {
|
||||
(
|
||||
if member_name.len() > 1 && string::is_upper(member_name) {
|
||||
if name.len() > 1 && string::is_upper(name) {
|
||||
// Ex) `CONSTANT`
|
||||
Prefix::Constants
|
||||
} else if member_name
|
||||
} else if name
|
||||
.chars()
|
||||
.next()
|
||||
.map(|char| char.is_uppercase())
|
||||
@@ -29,6 +35,7 @@ pub fn member_key(member_name: &str) -> (Prefix, String) {
|
||||
// Ex) `variable`
|
||||
Prefix::Variables
|
||||
},
|
||||
member_name.to_lowercase(),
|
||||
name.to_lowercase(),
|
||||
asname,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
|
||||
#[derive(Debug, Hash, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct ImportFromData<'a> {
|
||||
@@ -48,16 +48,19 @@ impl Importable for ImportFromData<'_> {
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ImportBlock<'a> {
|
||||
// Map from (module, level) to `AliasData`.
|
||||
pub import_from: BTreeMap<ImportFromData<'a>, BTreeSet<AliasData<'a>>>,
|
||||
// Set of (name, asname).
|
||||
pub import: BTreeSet<AliasData<'a>>,
|
||||
// Set of (name, asname), used to track regular imports.
|
||||
// Ex) `import module`
|
||||
pub import: FnvHashSet<AliasData<'a>>,
|
||||
// Map from (module, level) to `AliasData`, used to track 'from' imports.
|
||||
// Ex) `from module import member`
|
||||
pub import_from: FnvHashMap<ImportFromData<'a>, FnvHashSet<AliasData<'a>>>,
|
||||
// Set of (module, level, name, asname), used to track re-exported 'from' imports.
|
||||
// Ex) `from module import member as member`
|
||||
pub import_from_as: FnvHashSet<(ImportFromData<'a>, AliasData<'a>)>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct OrderedImportBlock<'a> {
|
||||
// Map from (module, level) to `AliasData`.
|
||||
pub import_from: Vec<(ImportFromData<'a>, Vec<AliasData<'a>>)>,
|
||||
// Set of (name, asname).
|
||||
pub import: Vec<AliasData<'a>>,
|
||||
pub import_from: Vec<(ImportFromData<'a>, Vec<AliasData<'a>>)>,
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ mod pyupgrade;
|
||||
mod rules;
|
||||
pub mod settings;
|
||||
pub mod source_code_locator;
|
||||
#[cfg(feature = "update-informer")]
|
||||
pub mod updates;
|
||||
pub mod visibility;
|
||||
|
||||
/// Run Ruff over Python source code directly.
|
||||
|
||||
@@ -336,6 +336,7 @@ mod tests {
|
||||
#[test_case(CheckCode::B009, Path::new("B009_B010.py"); "B009")]
|
||||
#[test_case(CheckCode::B010, Path::new("B009_B010.py"); "B010")]
|
||||
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
|
||||
#[test_case(CheckCode::B012, Path::new("B012.py"); "B012")]
|
||||
#[test_case(CheckCode::B013, Path::new("B013.py"); "B013")]
|
||||
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
||||
#[test_case(CheckCode::B015, Path::new("B015.py"); "B015")]
|
||||
@@ -343,8 +344,13 @@ mod tests {
|
||||
#[test_case(CheckCode::B017, Path::new("B017.py"); "B017")]
|
||||
#[test_case(CheckCode::B018, Path::new("B018.py"); "B018")]
|
||||
#[test_case(CheckCode::B019, Path::new("B019.py"); "B019")]
|
||||
#[test_case(CheckCode::B020, Path::new("B020.py"); "B020")]
|
||||
#[test_case(CheckCode::B021, Path::new("B021.py"); "B021")]
|
||||
#[test_case(CheckCode::B022, Path::new("B022.py"); "B022")]
|
||||
#[test_case(CheckCode::B024, Path::new("B024.py"); "B024")]
|
||||
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
|
||||
#[test_case(CheckCode::B026, Path::new("B026.py"); "B026")]
|
||||
#[test_case(CheckCode::B027, Path::new("B027.py"); "B027")]
|
||||
#[test_case(CheckCode::C400, Path::new("C400.py"); "C400")]
|
||||
#[test_case(CheckCode::C401, Path::new("C401.py"); "C401")]
|
||||
#[test_case(CheckCode::C402, Path::new("C402.py"); "C402")]
|
||||
@@ -425,6 +431,7 @@ mod tests {
|
||||
#[test_case(CheckCode::F401, Path::new("F401_3.py"); "F401_3")]
|
||||
#[test_case(CheckCode::F401, Path::new("F401_4.py"); "F401_4")]
|
||||
#[test_case(CheckCode::F401, Path::new("F401_5.py"); "F401_5")]
|
||||
#[test_case(CheckCode::F401, Path::new("F401_6.py"); "F401_6")]
|
||||
#[test_case(CheckCode::F402, Path::new("F402.py"); "F402")]
|
||||
#[test_case(CheckCode::F403, Path::new("F403.py"); "F403")]
|
||||
#[test_case(CheckCode::F404, Path::new("F404.py"); "F404")]
|
||||
@@ -449,6 +456,8 @@ mod tests {
|
||||
#[test_case(CheckCode::F821, Path::new("F821_1.py"); "F821_1")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_2.py"); "F821_2")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_3.py"); "F821_3")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_4.py"); "F821_4")]
|
||||
#[test_case(CheckCode::F821, Path::new("F821_5.py"); "F821_5")]
|
||||
#[test_case(CheckCode::F822, Path::new("F822.py"); "F822")]
|
||||
#[test_case(CheckCode::F823, Path::new("F823.py"); "F823")]
|
||||
#[test_case(CheckCode::F831, Path::new("F831.py"); "F831")]
|
||||
@@ -515,7 +524,7 @@ mod tests {
|
||||
let snapshot = format!("{}_{}", check_code.as_ref(), path.to_string_lossy());
|
||||
let mut checks = test_path(
|
||||
Path::new("./resources/test/fixtures").join(path).as_path(),
|
||||
&settings::Settings::for_rule(check_code.clone()),
|
||||
&settings::Settings::for_rule(check_code),
|
||||
&fixer::Mode::Generate,
|
||||
)?;
|
||||
checks.sort_by_key(|check| check.location);
|
||||
|
||||
38
src/main.rs
38
src/main.rs
@@ -17,6 +17,8 @@ use ::ruff::settings::configuration::Configuration;
|
||||
use ::ruff::settings::types::FilePattern;
|
||||
use ::ruff::settings::user::UserConfiguration;
|
||||
use ::ruff::settings::{pyproject, Settings};
|
||||
#[cfg(feature = "update-informer")]
|
||||
use ::ruff::updates;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
@@ -26,11 +28,6 @@ use notify::{raw_watcher, RecursiveMode, Watcher};
|
||||
use rayon::prelude::*;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
#[cfg(feature = "update-informer")]
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Shim that calls par_iter except for wasm because there's no wasm support in
|
||||
/// rayon yet (there is a shim to be used for the web, but it requires js
|
||||
/// cooperation) Unfortunately, ParallelIterator does not implement Iterator so
|
||||
@@ -45,30 +42,6 @@ fn par_iter<T: Sync>(iterable: &Vec<T>) -> impl Iterator<Item = &T> {
|
||||
iterable.iter()
|
||||
}
|
||||
|
||||
#[cfg(feature = "update-informer")]
|
||||
fn check_for_updates() {
|
||||
use update_informer::{registry, Check};
|
||||
|
||||
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
|
||||
|
||||
if let Some(new_version) = informer.check_version().ok().flatten() {
|
||||
let msg = format!(
|
||||
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
|
||||
pkg_name = CARGO_PKG_NAME.italic().cyan(),
|
||||
pkg_version = CARGO_PKG_VERSION,
|
||||
new_version = new_version.to_string().green()
|
||||
);
|
||||
|
||||
let cmd = format!(
|
||||
"Run to update: {cmd} {pkg_name}",
|
||||
cmd = "pip3 install --upgrade".green(),
|
||||
pkg_name = CARGO_PKG_NAME.green()
|
||||
);
|
||||
|
||||
println!("\n{msg}\n{cmd}");
|
||||
}
|
||||
}
|
||||
|
||||
fn show_settings(
|
||||
configuration: Configuration,
|
||||
project_root: Option<PathBuf>,
|
||||
@@ -292,6 +265,9 @@ fn inner_main() -> Result<ExitCode> {
|
||||
if !cli.extend_ignore.is_empty() {
|
||||
configuration.extend_ignore = cli.extend_ignore;
|
||||
}
|
||||
if let Some(line_length) = cli.line_length {
|
||||
configuration.line_length = line_length;
|
||||
}
|
||||
if let Some(target_version) = cli.target_version {
|
||||
configuration.target_version = target_version;
|
||||
}
|
||||
@@ -402,8 +378,8 @@ fn inner_main() -> Result<ExitCode> {
|
||||
|
||||
// Check for updates if we're in a non-silent log level.
|
||||
#[cfg(feature = "update-informer")]
|
||||
if !is_stdin && log_level >= LogLevel::Default {
|
||||
check_for_updates();
|
||||
if !is_stdin && log_level >= LogLevel::Default && atty::is(atty::Stream::Stdout) {
|
||||
let _ = updates::check_for_updates();
|
||||
}
|
||||
|
||||
if messages.iter().any(|message| !message.fixed) && !cli.exit_zero {
|
||||
|
||||
18
src/noqa.rs
18
src/noqa.rs
@@ -10,7 +10,7 @@ use regex::Regex;
|
||||
use crate::checks::{Check, CheckCode};
|
||||
|
||||
static NO_QA_REGEX: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r"(?P<noqa>\s*# noqa(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)")
|
||||
Regex::new(r"(?P<noqa>\s*(?i:# noqa)(?::\s?(?P<codes>([A-Z]+[0-9]+(?:[,\s]+)?)+))?)")
|
||||
.expect("Invalid regex")
|
||||
});
|
||||
static SPLIT_COMMA_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"[,\s]").expect("Invalid regex"));
|
||||
@@ -118,7 +118,21 @@ mod tests {
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::noqa::add_noqa_inner;
|
||||
use crate::noqa::{add_noqa_inner, NO_QA_REGEX};
|
||||
|
||||
#[test]
|
||||
fn regex() {
|
||||
assert!(NO_QA_REGEX.is_match("# noqa"));
|
||||
assert!(NO_QA_REGEX.is_match("# NoQA"));
|
||||
|
||||
assert!(NO_QA_REGEX.is_match("# noqa: F401"));
|
||||
assert!(NO_QA_REGEX.is_match("# NoQA: F401"));
|
||||
assert!(NO_QA_REGEX.is_match("# noqa: F401, E501"));
|
||||
|
||||
assert!(NO_QA_REGEX.is_match("# noqa:F401"));
|
||||
assert!(NO_QA_REGEX.is_match("# NoQA:F401"));
|
||||
assert!(NO_QA_REGEX.is_match("# noqa:F401, E501"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modification() -> Result<()> {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use rustpython_ast::{Arguments, Expr, ExprKind, Stmt};
|
||||
|
||||
use crate::ast::types::{FunctionScope, Range, Scope, ScopeKind};
|
||||
use crate::ast::types::{Range, Scope, ScopeKind};
|
||||
use crate::checks::{Check, CheckKind};
|
||||
use crate::pep8_naming::helpers;
|
||||
use crate::pep8_naming::helpers::FunctionType;
|
||||
use crate::pep8_naming::settings::Settings;
|
||||
use crate::python::string;
|
||||
use crate::python::string::{self};
|
||||
|
||||
/// N801
|
||||
pub fn invalid_class_name(class_def: &Stmt, name: &str) -> Option<Check> {
|
||||
@@ -100,20 +100,6 @@ pub fn invalid_first_argument_name_for_method(
|
||||
None
|
||||
}
|
||||
|
||||
/// N806
|
||||
pub fn non_lowercase_variable_in_function(scope: &Scope, expr: &Expr, name: &str) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Function(FunctionScope { .. })) {
|
||||
return None;
|
||||
}
|
||||
if name.to_lowercase() != name {
|
||||
return Some(Check::new(
|
||||
CheckKind::NonLowercaseVariableInFunction(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N807
|
||||
pub fn dunder_function_name(scope: &Scope, stmt: &Stmt, name: &str) -> Option<Check> {
|
||||
if matches!(scope.kind, ScopeKind::Class(_)) {
|
||||
@@ -192,38 +178,6 @@ pub fn camelcase_imported_as_constant(
|
||||
None
|
||||
}
|
||||
|
||||
/// N815
|
||||
pub fn mixed_case_variable_in_class_scope(scope: &Scope, expr: &Expr, name: &str) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Class(_)) {
|
||||
return None;
|
||||
}
|
||||
if helpers::is_mixed_case(name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::MixedCaseVariableInClassScope(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N816
|
||||
pub fn mixed_case_variable_in_global_scope(
|
||||
scope: &Scope,
|
||||
expr: &Expr,
|
||||
name: &str,
|
||||
) -> Option<Check> {
|
||||
if !matches!(scope.kind, ScopeKind::Module) {
|
||||
return None;
|
||||
}
|
||||
if helpers::is_mixed_case(name) {
|
||||
return Some(Check::new(
|
||||
CheckKind::MixedCaseVariableInGlobalScope(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// N817
|
||||
pub fn camelcase_imported_as_acronym(
|
||||
import_from: &Stmt,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use itertools::Itertools;
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
use rustpython_ast::{Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
use crate::ast::helpers::match_name_or_attr;
|
||||
use crate::ast::helpers::{collect_call_paths, match_call_path, match_name_or_attr};
|
||||
use crate::ast::types::{Scope, ScopeKind};
|
||||
use crate::pep8_naming::settings::Settings;
|
||||
use crate::python::string::{is_lower, is_upper};
|
||||
@@ -78,12 +79,28 @@ pub fn is_acronym(name: &str, asname: &str) -> bool {
|
||||
name.chars().filter(|c| c.is_uppercase()).join("") == asname
|
||||
}
|
||||
|
||||
pub fn is_namedtuple_assignment(
|
||||
stmt: &Stmt,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
) -> bool {
|
||||
if let StmtKind::Assign { value, .. } = &stmt.node {
|
||||
match_call_path(
|
||||
&collect_call_paths(value),
|
||||
"collections",
|
||||
"namedtuple",
|
||||
from_imports,
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::pep8_naming::helpers::{is_acronym, is_camelcase, is_mixed_case};
|
||||
|
||||
#[test]
|
||||
fn test_is_camelcase() -> () {
|
||||
fn test_is_camelcase() {
|
||||
assert!(is_camelcase("Camel"));
|
||||
assert!(is_camelcase("CamelCase"));
|
||||
assert!(!is_camelcase("camel"));
|
||||
@@ -93,7 +110,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_mixed_case() -> () {
|
||||
fn test_is_mixed_case() {
|
||||
assert!(is_mixed_case("mixedCase"));
|
||||
assert!(is_mixed_case("mixed_Case"));
|
||||
assert!(is_mixed_case("_mixed_Case"));
|
||||
@@ -104,7 +121,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_acronym() -> () {
|
||||
fn test_is_acronym() {
|
||||
assert!(is_acronym("AB", "AB"));
|
||||
assert!(is_acronym("AbcDef", "AD"));
|
||||
assert!(!is_acronym("AbcDef", "Ad"));
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod checks;
|
||||
mod helpers;
|
||||
pub mod plugins;
|
||||
pub mod settings;
|
||||
|
||||
58
src/pep8_naming/plugins.rs
Normal file
58
src/pep8_naming/plugins.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use rustpython_ast::{Expr, Stmt};
|
||||
|
||||
use crate::ast::types::Range;
|
||||
use crate::check_ast::Checker;
|
||||
use crate::checks::CheckKind;
|
||||
use crate::pep8_naming::helpers;
|
||||
use crate::Check;
|
||||
|
||||
/// N806
|
||||
pub fn non_lowercase_variable_in_function(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
) {
|
||||
if name.to_lowercase() != name
|
||||
&& !helpers::is_namedtuple_assignment(stmt, &checker.from_imports)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::NonLowercaseVariableInFunction(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// N815
|
||||
pub fn mixed_case_variable_in_class_scope(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
) {
|
||||
if helpers::is_mixed_case(name)
|
||||
&& !helpers::is_namedtuple_assignment(stmt, &checker.from_imports)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MixedCaseVariableInClassScope(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/// N816
|
||||
pub fn mixed_case_variable_in_global_scope(
|
||||
checker: &mut Checker,
|
||||
expr: &Expr,
|
||||
stmt: &Stmt,
|
||||
name: &str,
|
||||
) {
|
||||
if helpers::is_mixed_case(name)
|
||||
&& !helpers::is_namedtuple_assignment(stmt, &checker.from_imports)
|
||||
{
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::MixedCaseVariableInGlobalScope(name.to_string()),
|
||||
Range::from_located(expr),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use itertools::Itertools;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
@@ -696,7 +697,7 @@ pub fn ends_with_period(checker: &mut Checker, definition: &Definition) {
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
if let Some(string) = string.lines().next() {
|
||||
if let Some(string) = string.trim().lines().next() {
|
||||
if !string.ends_with('.') {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::EndsInPeriod,
|
||||
@@ -806,7 +807,7 @@ pub fn ends_with_punctuation(checker: &mut Checker, definition: &Definition) {
|
||||
..
|
||||
} = &docstring.node
|
||||
{
|
||||
if let Some(string) = string.lines().next() {
|
||||
if let Some(string) = string.trim().lines().next() {
|
||||
if !(string.ends_with('.') || string.ends_with('!') || string.ends_with('?')) {
|
||||
checker.add_check(Check::new(
|
||||
CheckKind::EndsInPunctuation,
|
||||
@@ -1287,7 +1288,11 @@ fn common_section(
|
||||
blanks_and_section_underline(checker, definition, context);
|
||||
}
|
||||
|
||||
fn missing_args(checker: &mut Checker, definition: &Definition, docstrings_args: &BTreeSet<&str>) {
|
||||
fn missing_args(
|
||||
checker: &mut Checker,
|
||||
definition: &Definition,
|
||||
docstrings_args: &FnvHashSet<&str>,
|
||||
) {
|
||||
if let DefinitionKind::Function(parent)
|
||||
| DefinitionKind::NestedFunction(parent)
|
||||
| DefinitionKind::Method(parent) = definition.kind
|
||||
@@ -1377,7 +1382,7 @@ fn args_section(checker: &mut Checker, definition: &Definition, context: &Sectio
|
||||
checker,
|
||||
definition,
|
||||
// Collect the list of arguments documented in the docstring.
|
||||
&BTreeSet::from_iter(args_sections.iter().filter_map(|section| {
|
||||
&FnvHashSet::from_iter(args_sections.iter().filter_map(|section| {
|
||||
match GOOGLE_ARGS_REGEX.captures(section.as_str()) {
|
||||
Some(caps) => caps.get(1).map(|arg_name| arg_name.as_str()),
|
||||
None => None,
|
||||
@@ -1388,7 +1393,7 @@ fn args_section(checker: &mut Checker, definition: &Definition, context: &Sectio
|
||||
|
||||
fn parameters_section(checker: &mut Checker, definition: &Definition, context: &SectionContext) {
|
||||
// Collect the list of arguments documented in the docstring.
|
||||
let mut docstring_args: BTreeSet<&str> = Default::default();
|
||||
let mut docstring_args: FnvHashSet<&str> = FnvHashSet::default();
|
||||
let section_level_indent = helpers::leading_space(context.line);
|
||||
for i in 1..context.following_lines.len() {
|
||||
let current_line = context.following_lines[i - 1];
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use regex::Regex;
|
||||
use rustpython_parser::ast::{
|
||||
Arg, Arguments, Constant, Excepthandler, ExcepthandlerKind, Expr, ExprKind, Stmt, StmtKind,
|
||||
@@ -113,7 +112,7 @@ pub fn duplicate_arguments(arguments: &Arguments) -> Vec<Check> {
|
||||
}
|
||||
|
||||
// Search for duplicates.
|
||||
let mut idents: BTreeSet<&str> = BTreeSet::new();
|
||||
let mut idents: FnvHashSet<&str> = FnvHashSet::default();
|
||||
for arg in all_arguments {
|
||||
let ident = &arg.node.arg;
|
||||
if idents.contains(ident.as_str()) {
|
||||
|
||||
@@ -27,7 +27,7 @@ mod tests {
|
||||
use crate::python::string::{is_lower, is_upper};
|
||||
|
||||
#[test]
|
||||
fn test_is_lower() -> () {
|
||||
fn test_is_lower() {
|
||||
assert!(is_lower("abc"));
|
||||
assert!(is_lower("a_b_c"));
|
||||
assert!(is_lower("a2c"));
|
||||
@@ -38,7 +38,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_upper() -> () {
|
||||
fn test_is_upper() {
|
||||
assert!(is_upper("ABC"));
|
||||
assert!(is_upper("A_B_C"));
|
||||
assert!(is_upper("A2C"));
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use fnv::FnvHashSet;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
// See: https://pycqa.github.io/isort/docs/configuration/options.html#known-standard-library
|
||||
pub static KNOWN_STANDARD_LIBRARY: Lazy<BTreeSet<&'static str>> = Lazy::new(|| {
|
||||
BTreeSet::from([
|
||||
pub static KNOWN_STANDARD_LIBRARY: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
|
||||
FnvHashSet::from_iter([
|
||||
"_ast",
|
||||
"_dummy_thread",
|
||||
"_thread",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use rustpython_ast::{Expr, ExprKind};
|
||||
use rustpython_ast::Expr;
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path, match_call_path};
|
||||
|
||||
// See: https://pypi.org/project/typing-extensions/
|
||||
static TYPING_EXTENSIONS: Lazy<FnvHashSet<&'static str>> = Lazy::new(|| {
|
||||
@@ -63,144 +65,144 @@ pub fn in_extensions(name: &str) -> bool {
|
||||
}
|
||||
|
||||
// See: https://docs.python.org/3/library/typing.html
|
||||
static IMPORTED_SUBSCRIPTS: Lazy<FnvHashMap<&'static str, FnvHashSet<&'static str>>> =
|
||||
Lazy::new(|| {
|
||||
let mut import_map = FnvHashMap::default();
|
||||
for (name, module) in [
|
||||
// `collections`
|
||||
("ChainMap", "collections"),
|
||||
("Counter", "collections"),
|
||||
("OrderedDict", "collections"),
|
||||
("defaultdict", "collections"),
|
||||
("deque", "collections"),
|
||||
// `collections.abc`
|
||||
("AsyncGenerator", "collections.abc"),
|
||||
("AsyncIterable", "collections.abc"),
|
||||
("AsyncIterator", "collections.abc"),
|
||||
("Awaitable", "collections.abc"),
|
||||
("ByteString", "collections.abc"),
|
||||
("Callable", "collections.abc"),
|
||||
("Collection", "collections.abc"),
|
||||
("Container", "collections.abc"),
|
||||
("Coroutine", "collections.abc"),
|
||||
("Generator", "collections.abc"),
|
||||
("ItemsView", "collections.abc"),
|
||||
("Iterable", "collections.abc"),
|
||||
("Iterator", "collections.abc"),
|
||||
("KeysView", "collections.abc"),
|
||||
("Mapping", "collections.abc"),
|
||||
("MappingView", "collections.abc"),
|
||||
("MutableMapping", "collections.abc"),
|
||||
("MutableSequence", "collections.abc"),
|
||||
("MutableSet", "collections.abc"),
|
||||
("Reversible", "collections.abc"),
|
||||
("Sequence", "collections.abc"),
|
||||
("Set", "collections.abc"),
|
||||
("ValuesView", "collections.abc"),
|
||||
// `contextlib`
|
||||
("AbstractAsyncContextManager", "contextlib"),
|
||||
("AbstractContextManager", "contextlib"),
|
||||
// `re`
|
||||
("Match", "re"),
|
||||
("Pattern", "re"),
|
||||
// `typing`
|
||||
("AbstractSet", "typing"),
|
||||
("Annotated", "typing"),
|
||||
("AsyncContextManager", "typing"),
|
||||
("AsyncGenerator", "typing"),
|
||||
("AsyncIterator", "typing"),
|
||||
("Awaitable", "typing"),
|
||||
("BinaryIO", "typing"),
|
||||
("ByteString", "typing"),
|
||||
("Callable", "typing"),
|
||||
("ChainMap", "typing"),
|
||||
("ClassVar", "typing"),
|
||||
("Collection", "typing"),
|
||||
("Concatenate", "typing"),
|
||||
("Container", "typing"),
|
||||
("ContextManager", "typing"),
|
||||
("Coroutine", "typing"),
|
||||
("Counter", "typing"),
|
||||
("DefaultDict", "typing"),
|
||||
("Deque", "typing"),
|
||||
("Dict", "typing"),
|
||||
("Final", "typing"),
|
||||
("FrozenSet", "typing"),
|
||||
("Generator", "typing"),
|
||||
("Generic", "typing"),
|
||||
("IO", "typing"),
|
||||
("ItemsView", "typing"),
|
||||
("Iterable", "typing"),
|
||||
("Iterator", "typing"),
|
||||
("KeysView", "typing"),
|
||||
("List", "typing"),
|
||||
("Mapping", "typing"),
|
||||
("Match", "typing"),
|
||||
("MutableMapping", "typing"),
|
||||
("MutableSequence", "typing"),
|
||||
("MutableSet", "typing"),
|
||||
("Optional", "typing"),
|
||||
("OrderedDict", "typing"),
|
||||
("Pattern", "typing"),
|
||||
("Reversible", "typing"),
|
||||
("Sequence", "typing"),
|
||||
("Set", "typing"),
|
||||
("TextIO", "typing"),
|
||||
("Tuple", "typing"),
|
||||
("Type", "typing"),
|
||||
("TypeGuard", "typing"),
|
||||
("Union", "typing"),
|
||||
("Unpack", "typing"),
|
||||
("ValuesView", "typing"),
|
||||
// `typing.io`
|
||||
("BinaryIO", "typing.io"),
|
||||
("IO", "typing.io"),
|
||||
("TextIO", "typing.io"),
|
||||
// `typing.re`
|
||||
("Match", "typing.re"),
|
||||
("Pattern", "typing.re"),
|
||||
// `typing_extensions`
|
||||
("Annotated", "typing_extensions"),
|
||||
("AsyncContextManager", "typing_extensions"),
|
||||
("AsyncGenerator", "typing_extensions"),
|
||||
("AsyncIterable", "typing_extensions"),
|
||||
("AsyncIterator", "typing_extensions"),
|
||||
("Awaitable", "typing_extensions"),
|
||||
("ChainMap", "typing_extensions"),
|
||||
("ClassVar", "typing_extensions"),
|
||||
("Concatenate", "typing_extensions"),
|
||||
("ContextManager", "typing_extensions"),
|
||||
("Coroutine", "typing_extensions"),
|
||||
("Counter", "typing_extensions"),
|
||||
("DefaultDict", "typing_extensions"),
|
||||
("Deque", "typing_extensions"),
|
||||
("Type", "typing_extensions"),
|
||||
// `weakref`
|
||||
("WeakKeyDictionary", "weakref"),
|
||||
("WeakSet", "weakref"),
|
||||
("WeakValueDictionary", "weakref"),
|
||||
] {
|
||||
import_map
|
||||
.entry(name)
|
||||
.or_insert_with(FnvHashSet::default)
|
||||
.insert(module);
|
||||
}
|
||||
import_map
|
||||
});
|
||||
const SUBSCRIPTS: &[(&str, &str)] = &[
|
||||
// builtins
|
||||
("", "dict"),
|
||||
("", "frozenset"),
|
||||
("", "list"),
|
||||
("", "set"),
|
||||
("", "tuple"),
|
||||
("", "type"),
|
||||
// `collections`
|
||||
("collections", "ChainMap"),
|
||||
("collections", "Counter"),
|
||||
("collections", "OrderedDict"),
|
||||
("collections", "defaultdict"),
|
||||
("collections", "deque"),
|
||||
// `collections.abc`
|
||||
("collections.abc", "AsyncGenerator"),
|
||||
("collections.abc", "AsyncIterable"),
|
||||
("collections.abc", "AsyncIterator"),
|
||||
("collections.abc", "Awaitable"),
|
||||
("collections.abc", "ByteString"),
|
||||
("collections.abc", "Callable"),
|
||||
("collections.abc", "Collection"),
|
||||
("collections.abc", "Container"),
|
||||
("collections.abc", "Coroutine"),
|
||||
("collections.abc", "Generator"),
|
||||
("collections.abc", "ItemsView"),
|
||||
("collections.abc", "Iterable"),
|
||||
("collections.abc", "Iterator"),
|
||||
("collections.abc", "KeysView"),
|
||||
("collections.abc", "Mapping"),
|
||||
("collections.abc", "MappingView"),
|
||||
("collections.abc", "MutableMapping"),
|
||||
("collections.abc", "MutableSequence"),
|
||||
("collections.abc", "MutableSet"),
|
||||
("collections.abc", "Reversible"),
|
||||
("collections.abc", "Sequence"),
|
||||
("collections.abc", "Set"),
|
||||
("collections.abc", "ValuesView"),
|
||||
// `contextlib`
|
||||
("contextlib", "AbstractAsyncContextManager"),
|
||||
("contextlib", "AbstractContextManager"),
|
||||
// `re`
|
||||
("re", "Match"),
|
||||
("re", "Pattern"),
|
||||
// `typing`
|
||||
("typing", "AbstractSet"),
|
||||
("typing", "AsyncContextManager"),
|
||||
("typing", "AsyncGenerator"),
|
||||
("typing", "AsyncIterator"),
|
||||
("typing", "Awaitable"),
|
||||
("typing", "BinaryIO"),
|
||||
("typing", "ByteString"),
|
||||
("typing", "Callable"),
|
||||
("typing", "ChainMap"),
|
||||
("typing", "ClassVar"),
|
||||
("typing", "Collection"),
|
||||
("typing", "Concatenate"),
|
||||
("typing", "Container"),
|
||||
("typing", "ContextManager"),
|
||||
("typing", "Coroutine"),
|
||||
("typing", "Counter"),
|
||||
("typing", "DefaultDict"),
|
||||
("typing", "Deque"),
|
||||
("typing", "Dict"),
|
||||
("typing", "Final"),
|
||||
("typing", "FrozenSet"),
|
||||
("typing", "Generator"),
|
||||
("typing", "Generic"),
|
||||
("typing", "IO"),
|
||||
("typing", "ItemsView"),
|
||||
("typing", "Iterable"),
|
||||
("typing", "Iterator"),
|
||||
("typing", "KeysView"),
|
||||
("typing", "List"),
|
||||
("typing", "Mapping"),
|
||||
("typing", "Match"),
|
||||
("typing", "MutableMapping"),
|
||||
("typing", "MutableSequence"),
|
||||
("typing", "MutableSet"),
|
||||
("typing", "Optional"),
|
||||
("typing", "OrderedDict"),
|
||||
("typing", "Pattern"),
|
||||
("typing", "Reversible"),
|
||||
("typing", "Sequence"),
|
||||
("typing", "Set"),
|
||||
("typing", "TextIO"),
|
||||
("typing", "Tuple"),
|
||||
("typing", "Type"),
|
||||
("typing", "TypeGuard"),
|
||||
("typing", "Union"),
|
||||
("typing", "Unpack"),
|
||||
("typing", "ValuesView"),
|
||||
// `typing.io`
|
||||
("typing.io", "BinaryIO"),
|
||||
("typing.io", "IO"),
|
||||
("typing.io", "TextIO"),
|
||||
// `typing.re`
|
||||
("typing.re", "Match"),
|
||||
("typing.re", "Pattern"),
|
||||
// `typing_extensions`
|
||||
("typing_extensions", "AsyncContextManager"),
|
||||
("typing_extensions", "AsyncGenerator"),
|
||||
("typing_extensions", "AsyncIterable"),
|
||||
("typing_extensions", "AsyncIterator"),
|
||||
("typing_extensions", "Awaitable"),
|
||||
("typing_extensions", "ChainMap"),
|
||||
("typing_extensions", "ClassVar"),
|
||||
("typing_extensions", "Concatenate"),
|
||||
("typing_extensions", "ContextManager"),
|
||||
("typing_extensions", "Coroutine"),
|
||||
("typing_extensions", "Counter"),
|
||||
("typing_extensions", "DefaultDict"),
|
||||
("typing_extensions", "Deque"),
|
||||
("typing_extensions", "Type"),
|
||||
// `weakref`
|
||||
("weakref", "WeakKeyDictionary"),
|
||||
("weakref", "WeakSet"),
|
||||
("weakref", "WeakValueDictionary"),
|
||||
];
|
||||
|
||||
// See: https://docs.python.org/3/library/typing.html
|
||||
const PEP_583_SUBSCRIPTS: &[(&str, &str)] = &[
|
||||
// `typing`
|
||||
("typing", "Annotated"),
|
||||
// `typing_extensions`
|
||||
("typing_extensions", "Annotated"),
|
||||
];
|
||||
|
||||
// These are all assumed to come from the `typing` module.
|
||||
// See: https://peps.python.org/pep-0585/
|
||||
static PEP_585_BUILTINS_ELIGIBLE: Lazy<FnvHashSet<&'static str>> =
|
||||
Lazy::new(|| FnvHashSet::from_iter(["Dict", "FrozenSet", "List", "Set", "Tuple", "Type"]));
|
||||
|
||||
// These are all assumed to come from the `typing` module.
|
||||
// See: https://peps.python.org/pep-0585/
|
||||
static PEP_585_BUILTINS: Lazy<FnvHashSet<&'static str>> =
|
||||
Lazy::new(|| FnvHashSet::from_iter(["dict", "frozenset", "list", "set", "tuple", "type"]));
|
||||
|
||||
fn is_pep593_annotated_subscript(name: &str) -> bool {
|
||||
name == "Annotated"
|
||||
}
|
||||
const PEP_585_BUILTINS_ELIGIBLE: &[(&str, &str)] = &[
|
||||
("typing", "Dict"),
|
||||
("typing", "FrozenSet"),
|
||||
("typing", "List"),
|
||||
("typing", "Set"),
|
||||
("typing", "Tuple"),
|
||||
("typing", "Type"),
|
||||
("typing_extensions", "Type"),
|
||||
];
|
||||
|
||||
pub enum SubscriptKind {
|
||||
AnnotatedSubscript,
|
||||
@@ -209,72 +211,39 @@ pub enum SubscriptKind {
|
||||
|
||||
pub fn match_annotated_subscript(
|
||||
expr: &Expr,
|
||||
imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> Option<SubscriptKind> {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, value, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
// If `id` is `typing` and `attr` is `Union`, verify that `typing.Union` is an
|
||||
// annotated subscript.
|
||||
if IMPORTED_SUBSCRIPTS
|
||||
.get(&attr.as_str())
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return if is_pep593_annotated_subscript(attr) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
// Built-ins (no import necessary).
|
||||
if PEP_585_BUILTINS.contains(&id.as_str()) {
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
if !call_path.is_empty() {
|
||||
for (module, member) in SUBSCRIPTS {
|
||||
if match_call_path(&call_path, module, member, from_imports) {
|
||||
return Some(SubscriptKind::AnnotatedSubscript);
|
||||
}
|
||||
|
||||
// Verify that, e.g., `Union` is a reference to `typing.Union`.
|
||||
if let Some(modules) = IMPORTED_SUBSCRIPTS.get(&id.as_str()) {
|
||||
for module in modules {
|
||||
if imports
|
||||
.get(module)
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return if is_pep593_annotated_subscript(id) {
|
||||
Some(SubscriptKind::PEP593AnnotatedSubscript)
|
||||
} else {
|
||||
Some(SubscriptKind::AnnotatedSubscript)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
for (module, member) in PEP_583_SUBSCRIPTS {
|
||||
if match_call_path(&call_path, module, member, from_imports) {
|
||||
return Some(SubscriptKind::PEP593AnnotatedSubscript);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns `true` if `Expr` represents a reference to a typing object with a
|
||||
/// PEP 585 built-in. Note that none of the PEP 585 built-ins are in
|
||||
/// `typing_extensions`.
|
||||
pub fn is_pep585_builtin(expr: &Expr, typing_imports: Option<&FnvHashSet<&str>>) -> bool {
|
||||
match &expr.node {
|
||||
ExprKind::Attribute { attr, value, .. } => {
|
||||
if let ExprKind::Name { id, .. } = &value.node {
|
||||
id == "typing" && PEP_585_BUILTINS_ELIGIBLE.contains(&attr.as_str())
|
||||
} else {
|
||||
false
|
||||
/// PEP 585 built-in.
|
||||
pub fn is_pep585_builtin(
|
||||
expr: &Expr,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> bool {
|
||||
let call_path = dealias_call_path(collect_call_paths(expr), import_aliases);
|
||||
if !call_path.is_empty() {
|
||||
for (module, member) in PEP_585_BUILTINS_ELIGIBLE {
|
||||
if match_call_path(&call_path, module, member, from_imports) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
ExprKind::Name { id, .. } => {
|
||||
typing_imports
|
||||
.map(|imports| imports.contains(&id.as_str()))
|
||||
.unwrap_or_default()
|
||||
&& PEP_585_BUILTINS_ELIGIBLE.contains(&id.as_str())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use fnv::FnvHashSet;
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
use rustpython_ast::{Constant, KeywordData};
|
||||
use rustpython_parser::ast::{ArgData, Expr, ExprKind, Stmt, StmtKind};
|
||||
|
||||
@@ -163,7 +163,8 @@ pub fn type_of_primitive(func: &Expr, args: &[Expr], location: Range) -> Option<
|
||||
pub fn unnecessary_lru_cache_params(
|
||||
decorator_list: &[Expr],
|
||||
target_version: PythonVersion,
|
||||
imports: Option<&FnvHashSet<&str>>,
|
||||
from_imports: &FnvHashMap<&str, FnvHashSet<&str>>,
|
||||
import_aliases: &FnvHashMap<&str, &str>,
|
||||
) -> Option<Check> {
|
||||
for expr in decorator_list.iter() {
|
||||
if let ExprKind::Call {
|
||||
@@ -173,7 +174,13 @@ pub fn unnecessary_lru_cache_params(
|
||||
} = &expr.node
|
||||
{
|
||||
if args.is_empty()
|
||||
&& helpers::match_name_or_attr_from_module(func, "lru_cache", "functools", imports)
|
||||
&& helpers::match_module_member(
|
||||
func,
|
||||
"functools",
|
||||
"lru_cache",
|
||||
from_imports,
|
||||
import_aliases,
|
||||
)
|
||||
{
|
||||
// Ex) `functools.lru_cache()`
|
||||
if keywords.is_empty() {
|
||||
|
||||
@@ -8,7 +8,8 @@ pub fn unnecessary_lru_cache_params(checker: &mut Checker, decorator_list: &[Exp
|
||||
if let Some(mut check) = checks::unnecessary_lru_cache_params(
|
||||
decorator_list,
|
||||
checker.settings.target_version,
|
||||
checker.from_imports.get("functools"),
|
||||
&checker.from_imports,
|
||||
&checker.import_aliases,
|
||||
) {
|
||||
if checker.patch() {
|
||||
if let Some(fix) =
|
||||
|
||||
@@ -7,13 +7,14 @@ use crate::checks::{Check, CheckKind};
|
||||
|
||||
/// U006
|
||||
pub fn use_pep585_annotation(checker: &mut Checker, expr: &Expr, id: &str) {
|
||||
let replacement = checker.import_aliases.get(id).unwrap_or(&id);
|
||||
let mut check = Check::new(
|
||||
CheckKind::UsePEP585Annotation(id.to_string()),
|
||||
CheckKind::UsePEP585Annotation(replacement.to_string()),
|
||||
Range::from_located(expr),
|
||||
);
|
||||
if checker.patch() {
|
||||
check.amend(Fix::replacement(
|
||||
id.to_lowercase(),
|
||||
replacement.to_lowercase(),
|
||||
expr.location,
|
||||
expr.end_location.unwrap(),
|
||||
));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use rustpython_ast::{Constant, Expr, ExprKind, Operator};
|
||||
|
||||
use crate::ast::helpers::{collect_call_paths, dealias_call_path};
|
||||
use crate::ast::types::Range;
|
||||
use crate::autofix::Fix;
|
||||
use crate::check_ast::Checker;
|
||||
@@ -43,7 +44,8 @@ fn union(elts: &[Expr]) -> Expr {
|
||||
|
||||
/// U007
|
||||
pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, slice: &Expr) {
|
||||
if checker.match_typing_module(value, "Optional") {
|
||||
let call_path = dealias_call_path(collect_call_paths(value), &checker.import_aliases);
|
||||
if checker.match_typing_call_path(&call_path, "Optional") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if checker.patch() {
|
||||
let mut generator = SourceGenerator::new();
|
||||
@@ -58,7 +60,7 @@ pub fn use_pep604_annotation(checker: &mut Checker, expr: &Expr, value: &Expr, s
|
||||
}
|
||||
}
|
||||
checker.add_check(check);
|
||||
} else if checker.match_typing_module(value, "Union") {
|
||||
} else if checker.match_typing_call_path(&call_path, "Union") {
|
||||
let mut check = Check::new(CheckKind::UsePEP604Annotation, Range::from_located(expr));
|
||||
if checker.patch() {
|
||||
match &slice.node {
|
||||
|
||||
@@ -114,6 +114,7 @@ impl Hash for Settings {
|
||||
self.target_version.hash(state);
|
||||
// Add plugin properties in alphabetical order.
|
||||
self.flake8_annotations.hash(state);
|
||||
self.flake8_bugbear.hash(state);
|
||||
self.flake8_quotes.hash(state);
|
||||
self.isort.hash(state);
|
||||
self.pep8_naming.hash(state);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
//! Options that the user can provide via pyproject.toml.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use fnv::FnvHashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
@@ -29,5 +28,5 @@ pub struct Options {
|
||||
pub isort: Option<isort::settings::Options>,
|
||||
pub pep8_naming: Option<pep8_naming::settings::Options>,
|
||||
// Tables are required to go last.
|
||||
pub per_file_ignores: Option<BTreeMap<String, Vec<CheckCodePrefix>>>,
|
||||
pub per_file_ignores: Option<FnvHashMap<String, Vec<CheckCodePrefix>>>,
|
||||
}
|
||||
|
||||
@@ -96,12 +96,12 @@ pub fn load_options(pyproject: &Option<PathBuf>) -> Result<Options> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use std::env::current_dir;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use fnv::FnvHashMap;
|
||||
|
||||
use crate::checks_gen::CheckCodePrefix;
|
||||
use crate::flake8_quotes::settings::Quote;
|
||||
@@ -346,7 +346,7 @@ other-attribute = 1
|
||||
extend_select: None,
|
||||
ignore: None,
|
||||
extend_ignore: None,
|
||||
per_file_ignores: Some(BTreeMap::from([(
|
||||
per_file_ignores: Some(FnvHashMap::from_iter([(
|
||||
"__init__.py".to_string(),
|
||||
vec![CheckCodePrefix::F401]
|
||||
),])),
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: range
|
||||
location:
|
||||
row: 85
|
||||
column: 60
|
||||
@@ -10,7 +11,8 @@ expression: checks
|
||||
row: 85
|
||||
column: 68
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: range
|
||||
location:
|
||||
row: 89
|
||||
column: 63
|
||||
@@ -18,7 +20,8 @@ expression: checks
|
||||
row: 89
|
||||
column: 71
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: range
|
||||
location:
|
||||
row: 93
|
||||
column: 59
|
||||
@@ -26,7 +29,8 @@ expression: checks
|
||||
row: 93
|
||||
column: 67
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: time.time
|
||||
location:
|
||||
row: 109
|
||||
column: 38
|
||||
@@ -34,7 +38,8 @@ expression: checks
|
||||
row: 109
|
||||
column: 49
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: dt.datetime.now
|
||||
location:
|
||||
row: 113
|
||||
column: 11
|
||||
@@ -42,7 +47,8 @@ expression: checks
|
||||
row: 113
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: dt.timedelta
|
||||
location:
|
||||
row: 113
|
||||
column: 31
|
||||
@@ -50,7 +56,8 @@ expression: checks
|
||||
row: 113
|
||||
column: 51
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: ~
|
||||
location:
|
||||
row: 117
|
||||
column: 29
|
||||
@@ -58,7 +65,8 @@ expression: checks
|
||||
row: 117
|
||||
column: 44
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: float
|
||||
location:
|
||||
row: 155
|
||||
column: 33
|
||||
@@ -66,7 +74,8 @@ expression: checks
|
||||
row: 155
|
||||
column: 47
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: float
|
||||
location:
|
||||
row: 160
|
||||
column: 29
|
||||
@@ -74,7 +83,8 @@ expression: checks
|
||||
row: 160
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: float
|
||||
location:
|
||||
row: 164
|
||||
column: 44
|
||||
@@ -82,7 +92,8 @@ expression: checks
|
||||
row: 164
|
||||
column: 57
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: float
|
||||
location:
|
||||
row: 170
|
||||
column: 20
|
||||
@@ -90,7 +101,8 @@ expression: checks
|
||||
row: 170
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: dt.datetime.now
|
||||
location:
|
||||
row: 170
|
||||
column: 30
|
||||
@@ -98,7 +110,8 @@ expression: checks
|
||||
row: 170
|
||||
column: 47
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: map
|
||||
location:
|
||||
row: 176
|
||||
column: 21
|
||||
@@ -106,7 +119,8 @@ expression: checks
|
||||
row: 176
|
||||
column: 62
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: random.randint
|
||||
location:
|
||||
row: 181
|
||||
column: 18
|
||||
@@ -114,7 +128,8 @@ expression: checks
|
||||
row: 181
|
||||
column: 59
|
||||
fix: ~
|
||||
- kind: FunctionCallArgumentDefault
|
||||
- kind:
|
||||
FunctionCallArgumentDefault: dt.datetime.now
|
||||
location:
|
||||
row: 181
|
||||
column: 36
|
||||
|
||||
95
src/snapshots/ruff__linter__tests__B012_B012.py.snap
Normal file
95
src/snapshots/ruff__linter__tests__B012_B012.py.snap
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
JumpStatementInFinally: return
|
||||
location:
|
||||
row: 5
|
||||
column: 8
|
||||
end_location:
|
||||
row: 5
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: return
|
||||
location:
|
||||
row: 13
|
||||
column: 12
|
||||
end_location:
|
||||
row: 13
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: return
|
||||
location:
|
||||
row: 21
|
||||
column: 12
|
||||
end_location:
|
||||
row: 21
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: return
|
||||
location:
|
||||
row: 31
|
||||
column: 12
|
||||
end_location:
|
||||
row: 31
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: return
|
||||
location:
|
||||
row: 44
|
||||
column: 20
|
||||
end_location:
|
||||
row: 44
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: break
|
||||
location:
|
||||
row: 66
|
||||
column: 12
|
||||
end_location:
|
||||
row: 66
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: continue
|
||||
location:
|
||||
row: 78
|
||||
column: 12
|
||||
end_location:
|
||||
row: 78
|
||||
column: 20
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: return
|
||||
location:
|
||||
row: 94
|
||||
column: 12
|
||||
end_location:
|
||||
row: 94
|
||||
column: 18
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: continue
|
||||
location:
|
||||
row: 101
|
||||
column: 8
|
||||
end_location:
|
||||
row: 101
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
JumpStatementInFinally: break
|
||||
location:
|
||||
row: 107
|
||||
column: 8
|
||||
end_location:
|
||||
row: 107
|
||||
column: 13
|
||||
fix: ~
|
||||
|
||||
32
src/snapshots/ruff__linter__tests__B020_B020.py.snap
Normal file
32
src/snapshots/ruff__linter__tests__B020_B020.py.snap
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
LoopVariableOverridesIterator: items
|
||||
location:
|
||||
row: 8
|
||||
column: 4
|
||||
end_location:
|
||||
row: 8
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
LoopVariableOverridesIterator: values
|
||||
location:
|
||||
row: 21
|
||||
column: 9
|
||||
end_location:
|
||||
row: 21
|
||||
column: 15
|
||||
fix: ~
|
||||
- kind:
|
||||
LoopVariableOverridesIterator: vars
|
||||
location:
|
||||
row: 36
|
||||
column: 4
|
||||
end_location:
|
||||
row: 36
|
||||
column: 8
|
||||
fix: ~
|
||||
|
||||
85
src/snapshots/ruff__linter__tests__B021_B021.py.snap
Normal file
85
src/snapshots/ruff__linter__tests__B021_B021.py.snap
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 1
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
column: 3
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 14
|
||||
column: 4
|
||||
end_location:
|
||||
row: 14
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 22
|
||||
column: 4
|
||||
end_location:
|
||||
row: 22
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 30
|
||||
column: 4
|
||||
end_location:
|
||||
row: 30
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 38
|
||||
column: 4
|
||||
end_location:
|
||||
row: 38
|
||||
column: 28
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 46
|
||||
column: 4
|
||||
end_location:
|
||||
row: 46
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 54
|
||||
column: 4
|
||||
end_location:
|
||||
row: 54
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 62
|
||||
column: 4
|
||||
end_location:
|
||||
row: 62
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 70
|
||||
column: 4
|
||||
end_location:
|
||||
row: 70
|
||||
column: 24
|
||||
fix: ~
|
||||
- kind: FStringDocstring
|
||||
location:
|
||||
row: 74
|
||||
column: 4
|
||||
end_location:
|
||||
row: 74
|
||||
column: 48
|
||||
fix: ~
|
||||
|
||||
21
src/snapshots/ruff__linter__tests__B022_B022.py.snap
Normal file
21
src/snapshots/ruff__linter__tests__B022_B022.py.snap
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
source: src/linter.r
|
||||
expression: checks
|
||||
---
|
||||
- kind: UselessContextlibSuppress
|
||||
location:
|
||||
row: 9
|
||||
column: 5
|
||||
end_location:
|
||||
row: 9
|
||||
column: 26
|
||||
fix: ~
|
||||
- kind: UselessContextlibSuppress
|
||||
location:
|
||||
row: 12
|
||||
column: 5
|
||||
end_location:
|
||||
row: 12
|
||||
column: 15
|
||||
fix: ~
|
||||
|
||||
59
src/snapshots/ruff__linter__tests__B024_B024.py.snap
Normal file
59
src/snapshots/ruff__linter__tests__B024_B024.py.snap
Normal file
@@ -0,0 +1,59 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: Base_1
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 22
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: MetaBase_1
|
||||
location:
|
||||
row: 58
|
||||
column: 0
|
||||
end_location:
|
||||
row: 63
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: abc_Base_1
|
||||
location:
|
||||
row: 69
|
||||
column: 0
|
||||
end_location:
|
||||
row: 74
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: abc_Base_2
|
||||
location:
|
||||
row: 74
|
||||
column: 0
|
||||
end_location:
|
||||
row: 79
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: notabc_Base_1
|
||||
location:
|
||||
row: 79
|
||||
column: 0
|
||||
end_location:
|
||||
row: 84
|
||||
column: 0
|
||||
fix: ~
|
||||
- kind:
|
||||
AbstractBaseClassWithoutAbstractMethod: abc_set_class_variable_4
|
||||
location:
|
||||
row: 128
|
||||
column: 0
|
||||
end_location:
|
||||
row: 130
|
||||
column: 0
|
||||
fix: ~
|
||||
|
||||
41
src/snapshots/ruff__linter__tests__B027_B027.py.snap
Normal file
41
src/snapshots/ruff__linter__tests__B027_B027.py.snap
Normal file
@@ -0,0 +1,41 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
EmptyMethodWithoutAbstractDecorator: AbstractClass
|
||||
location:
|
||||
row: 12
|
||||
column: 4
|
||||
end_location:
|
||||
row: 15
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind:
|
||||
EmptyMethodWithoutAbstractDecorator: AbstractClass
|
||||
location:
|
||||
row: 15
|
||||
column: 4
|
||||
end_location:
|
||||
row: 18
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind:
|
||||
EmptyMethodWithoutAbstractDecorator: AbstractClass
|
||||
location:
|
||||
row: 18
|
||||
column: 4
|
||||
end_location:
|
||||
row: 22
|
||||
column: 4
|
||||
fix: ~
|
||||
- kind:
|
||||
EmptyMethodWithoutAbstractDecorator: AbstractClass
|
||||
location:
|
||||
row: 22
|
||||
column: 4
|
||||
end_location:
|
||||
row: 29
|
||||
column: 4
|
||||
fix: ~
|
||||
|
||||
@@ -2,30 +2,6 @@
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 124
|
||||
column: 4
|
||||
end_location:
|
||||
row: 126
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 283
|
||||
column: 4
|
||||
end_location:
|
||||
row: 283
|
||||
column: 33
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 288
|
||||
column: 4
|
||||
end_location:
|
||||
row: 288
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: EndsInPeriod
|
||||
location:
|
||||
row: 350
|
||||
|
||||
@@ -2,30 +2,6 @@
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 124
|
||||
column: 4
|
||||
end_location:
|
||||
row: 126
|
||||
column: 7
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 283
|
||||
column: 4
|
||||
end_location:
|
||||
row: 283
|
||||
column: 33
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 288
|
||||
column: 4
|
||||
end_location:
|
||||
row: 288
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind: EndsInPunctuation
|
||||
location:
|
||||
row: 350
|
||||
|
||||
85
src/snapshots/ruff__linter__tests__F401_F401_6.py.snap
Normal file
85
src/snapshots/ruff__linter__tests__F401_F401_6.py.snap
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UnusedImport:
|
||||
- - background.BackgroundTasks
|
||||
- false
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 7
|
||||
column: 39
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 8
|
||||
column: 0
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport:
|
||||
- - datastructures.UploadFile
|
||||
- false
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 10
|
||||
column: 52
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 10
|
||||
column: 0
|
||||
end_location:
|
||||
row: 11
|
||||
column: 0
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport:
|
||||
- - background
|
||||
- false
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 17
|
||||
column: 17
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 17
|
||||
column: 0
|
||||
end_location:
|
||||
row: 18
|
||||
column: 0
|
||||
applied: false
|
||||
- kind:
|
||||
UnusedImport:
|
||||
- - datastructures
|
||||
- false
|
||||
location:
|
||||
row: 20
|
||||
column: 0
|
||||
end_location:
|
||||
row: 20
|
||||
column: 35
|
||||
fix:
|
||||
patch:
|
||||
content: ""
|
||||
location:
|
||||
row: 20
|
||||
column: 0
|
||||
end_location:
|
||||
row: 21
|
||||
column: 0
|
||||
applied: false
|
||||
|
||||
50
src/snapshots/ruff__linter__tests__F821_F821_4.py.snap
Normal file
50
src/snapshots/ruff__linter__tests__F821_F821_4.py.snap
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 4
|
||||
column: 9
|
||||
end_location:
|
||||
row: 4
|
||||
column: 16
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 9
|
||||
column: 10
|
||||
end_location:
|
||||
row: 9
|
||||
column: 17
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 14
|
||||
column: 14
|
||||
end_location:
|
||||
row: 14
|
||||
column: 21
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 19
|
||||
column: 30
|
||||
end_location:
|
||||
row: 19
|
||||
column: 37
|
||||
fix: ~
|
||||
- kind:
|
||||
UndefinedName: Model
|
||||
location:
|
||||
row: 24
|
||||
column: 18
|
||||
end_location:
|
||||
row: 24
|
||||
column: 25
|
||||
fix: ~
|
||||
|
||||
14
src/snapshots/ruff__linter__tests__F821_F821_5.py.snap
Normal file
14
src/snapshots/ruff__linter__tests__F821_F821_5.py.snap
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
source: src/linter.rs
|
||||
expression: checks
|
||||
---
|
||||
- kind:
|
||||
UndefinedName: InnerClass
|
||||
location:
|
||||
row: 5
|
||||
column: 29
|
||||
end_location:
|
||||
row: 5
|
||||
column: 41
|
||||
fix: ~
|
||||
|
||||
@@ -5,19 +5,19 @@ expression: checks
|
||||
- kind:
|
||||
NonLowercaseVariableInFunction: Camel
|
||||
location:
|
||||
row: 3
|
||||
row: 7
|
||||
column: 4
|
||||
end_location:
|
||||
row: 3
|
||||
row: 7
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
NonLowercaseVariableInFunction: CONSTANT
|
||||
location:
|
||||
row: 4
|
||||
row: 8
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
row: 8
|
||||
column: 12
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -5,28 +5,28 @@ expression: checks
|
||||
- kind:
|
||||
MixedCaseVariableInClassScope: mixedCase
|
||||
location:
|
||||
row: 4
|
||||
row: 8
|
||||
column: 4
|
||||
end_location:
|
||||
row: 4
|
||||
row: 8
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind:
|
||||
MixedCaseVariableInClassScope: _mixedCase
|
||||
location:
|
||||
row: 5
|
||||
row: 9
|
||||
column: 4
|
||||
end_location:
|
||||
row: 5
|
||||
row: 9
|
||||
column: 14
|
||||
fix: ~
|
||||
- kind:
|
||||
MixedCaseVariableInClassScope: mixed_Case
|
||||
location:
|
||||
row: 6
|
||||
row: 10
|
||||
column: 4
|
||||
end_location:
|
||||
row: 6
|
||||
row: 10
|
||||
column: 14
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -5,28 +5,28 @@ expression: checks
|
||||
- kind:
|
||||
MixedCaseVariableInGlobalScope: mixedCase
|
||||
location:
|
||||
row: 3
|
||||
row: 6
|
||||
column: 0
|
||||
end_location:
|
||||
row: 3
|
||||
row: 6
|
||||
column: 9
|
||||
fix: ~
|
||||
- kind:
|
||||
MixedCaseVariableInGlobalScope: _mixedCase
|
||||
location:
|
||||
row: 4
|
||||
row: 7
|
||||
column: 0
|
||||
end_location:
|
||||
row: 4
|
||||
row: 7
|
||||
column: 10
|
||||
fix: ~
|
||||
- kind:
|
||||
MixedCaseVariableInGlobalScope: mixed_Case
|
||||
location:
|
||||
row: 5
|
||||
row: 8
|
||||
column: 0
|
||||
end_location:
|
||||
row: 5
|
||||
row: 8
|
||||
column: 10
|
||||
fix: ~
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ expression: checks
|
||||
column: 9
|
||||
end_location:
|
||||
row: 4
|
||||
column: 13
|
||||
column: 20
|
||||
fix:
|
||||
patch:
|
||||
content: list
|
||||
@@ -18,7 +18,7 @@ expression: checks
|
||||
column: 9
|
||||
end_location:
|
||||
row: 4
|
||||
column: 13
|
||||
column: 20
|
||||
applied: false
|
||||
- kind:
|
||||
UsePEP585Annotation: List
|
||||
@@ -27,7 +27,7 @@ expression: checks
|
||||
column: 9
|
||||
end_location:
|
||||
row: 11
|
||||
column: 20
|
||||
column: 13
|
||||
fix:
|
||||
patch:
|
||||
content: list
|
||||
@@ -36,6 +36,42 @@ expression: checks
|
||||
column: 9
|
||||
end_location:
|
||||
row: 11
|
||||
column: 20
|
||||
column: 13
|
||||
applied: false
|
||||
- kind:
|
||||
UsePEP585Annotation: List
|
||||
location:
|
||||
row: 18
|
||||
column: 9
|
||||
end_location:
|
||||
row: 18
|
||||
column: 15
|
||||
fix:
|
||||
patch:
|
||||
content: list
|
||||
location:
|
||||
row: 18
|
||||
column: 9
|
||||
end_location:
|
||||
row: 18
|
||||
column: 15
|
||||
applied: false
|
||||
- kind:
|
||||
UsePEP585Annotation: List
|
||||
location:
|
||||
row: 25
|
||||
column: 9
|
||||
end_location:
|
||||
row: 25
|
||||
column: 14
|
||||
fix:
|
||||
patch:
|
||||
content: list
|
||||
location:
|
||||
row: 25
|
||||
column: 9
|
||||
end_location:
|
||||
row: 25
|
||||
column: 14
|
||||
applied: false
|
||||
|
||||
|
||||
@@ -18,4 +18,12 @@ expression: checks
|
||||
row: 7
|
||||
column: 13
|
||||
fix: ~
|
||||
- kind: SysVersionSlice3Referenced
|
||||
location:
|
||||
row: 8
|
||||
column: 6
|
||||
end_location:
|
||||
row: 8
|
||||
column: 7
|
||||
fix: ~
|
||||
|
||||
|
||||
75
src/updates.rs
Normal file
75
src/updates.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::fs::{create_dir_all, read_to_string, File};
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use colored::Colorize;
|
||||
|
||||
const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
|
||||
const CARGO_PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
fn cache_dir() -> &'static str {
|
||||
"./.ruff_cache"
|
||||
}
|
||||
|
||||
fn file_path() -> PathBuf {
|
||||
Path::new(cache_dir()).join(".update-informer")
|
||||
}
|
||||
|
||||
/// Get the "latest" version for which the user has been informed.
|
||||
fn get_latest() -> Result<Option<String>> {
|
||||
let path = file_path();
|
||||
if path.exists() {
|
||||
Ok(Some(read_to_string(path)?.trim().to_string()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the "latest" version for which the user has been informed.
|
||||
fn set_latest(version: &str) -> Result<()> {
|
||||
create_dir_all(cache_dir())?;
|
||||
let path = file_path();
|
||||
let mut file = File::create(path)?;
|
||||
file.write_all(version.trim().as_bytes())
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
|
||||
/// Update the user if a newer version is available.
|
||||
pub fn check_for_updates() -> Result<()> {
|
||||
use update_informer::{registry, Check};
|
||||
|
||||
let informer = update_informer::new(registry::PyPI, CARGO_PKG_NAME, CARGO_PKG_VERSION);
|
||||
|
||||
if let Some(new_version) = informer
|
||||
.check_version()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|version| version.to_string())
|
||||
{
|
||||
// If we've already notified the user about this version, return early.
|
||||
if let Some(latest_version) = get_latest()? {
|
||||
if latest_version == new_version {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
set_latest(&new_version)?;
|
||||
|
||||
let msg = format!(
|
||||
"A new version of {pkg_name} is available: v{pkg_version} -> {new_version}",
|
||||
pkg_name = CARGO_PKG_NAME.italic().cyan(),
|
||||
pkg_version = CARGO_PKG_VERSION,
|
||||
new_version = new_version.green()
|
||||
);
|
||||
|
||||
let cmd = format!(
|
||||
"Run to update: {cmd} {pkg_name}",
|
||||
cmd = "pip3 install --upgrade".green(),
|
||||
pkg_name = CARGO_PKG_NAME.green()
|
||||
);
|
||||
|
||||
println!("\n{msg}\n{cmd}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -6,7 +6,7 @@ use assert_cmd::{crate_name, Command};
|
||||
#[test]
|
||||
fn test_stdin_success() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
cmd.args(&["-"]).write_stdin("").assert().success();
|
||||
cmd.args(["-"]).write_stdin("").assert().success();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ fn test_stdin_success() -> Result<()> {
|
||||
fn test_stdin_error() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
let output = cmd
|
||||
.args(&["-"])
|
||||
.args(["-"])
|
||||
.write_stdin("import os\n")
|
||||
.assert()
|
||||
.failure();
|
||||
@@ -26,7 +26,7 @@ fn test_stdin_error() -> Result<()> {
|
||||
fn test_stdin_filename() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
let output = cmd
|
||||
.args(&["-", "--stdin-filename", "F401.py"])
|
||||
.args(["-", "--stdin-filename", "F401.py"])
|
||||
.write_stdin("import os\n")
|
||||
.assert()
|
||||
.failure();
|
||||
@@ -38,7 +38,7 @@ fn test_stdin_filename() -> Result<()> {
|
||||
fn test_stdin_autofix() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
let output = cmd
|
||||
.args(&["-", "--fix"])
|
||||
.args(["-", "--fix"])
|
||||
.write_stdin("import os\nimport sys\n\nprint(sys.version)\n")
|
||||
.assert()
|
||||
.success();
|
||||
@@ -53,7 +53,7 @@ fn test_stdin_autofix() -> Result<()> {
|
||||
fn test_stdin_autofix_when_not_fixable_should_still_print_contents() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
let output = cmd
|
||||
.args(&["-", "--fix"])
|
||||
.args(["-", "--fix"])
|
||||
.write_stdin("import os\nimport sys\n\nif (1, 2):\n print(sys.version)\n")
|
||||
.assert()
|
||||
.failure();
|
||||
@@ -68,7 +68,7 @@ fn test_stdin_autofix_when_not_fixable_should_still_print_contents() -> Result<(
|
||||
fn test_stdin_autofix_when_no_issues_should_still_print_contents() -> Result<()> {
|
||||
let mut cmd = Command::cargo_bin(crate_name!())?;
|
||||
let output = cmd
|
||||
.args(&["-", "--fix"])
|
||||
.args(["-", "--fix"])
|
||||
.write_stdin("import sys\n\nprint(sys.version)\n")
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
Reference in New Issue
Block a user