Compare commits
2 Commits
PYI034
...
micha/accu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
371e5fc31d | ||
|
|
1cefe505ea |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -16,7 +16,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
PACKAGE_NAME: ruff
|
||||
PYTHON_VERSION: "3.12"
|
||||
PYTHON_VERSION: "3.11"
|
||||
|
||||
jobs:
|
||||
determine_changes:
|
||||
|
||||
37
CHANGELOG.md
37
CHANGELOG.md
@@ -1,42 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## 0.7.3
|
||||
|
||||
### Preview features
|
||||
|
||||
- Formatter: Disallow single-line implicit concatenated strings ([#13928](https://github.com/astral-sh/ruff/pull/13928))
|
||||
- \[`flake8-pyi`\] Include all Python file types for `PYI006` and `PYI066` ([#14059](https://github.com/astral-sh/ruff/pull/14059))
|
||||
- \[`flake8-simplify`\] Implement `split-of-static-string` (`SIM905`) ([#14008](https://github.com/astral-sh/ruff/pull/14008))
|
||||
- \[`refurb`\] Implement `subclass-builtin` (`FURB189`) ([#14105](https://github.com/astral-sh/ruff/pull/14105))
|
||||
- \[`ruff`\] Improve diagnostic messages and docs (`RUF031`, `RUF032`, `RUF034`) ([#14068](https://github.com/astral-sh/ruff/pull/14068))
|
||||
|
||||
### Rule changes
|
||||
|
||||
- Detect items that hash to same value in duplicate sets (`B033`, `PLC0208`) ([#14064](https://github.com/astral-sh/ruff/pull/14064))
|
||||
- \[`eradicate`\] Better detection of IntelliJ language injection comments (`ERA001`) ([#14094](https://github.com/astral-sh/ruff/pull/14094))
|
||||
- \[`flake8-pyi`\] Add autofix for `docstring-in-stub` (`PYI021`) ([#14150](https://github.com/astral-sh/ruff/pull/14150))
|
||||
- \[`flake8-pyi`\] Update `duplicate-literal-member` (`PYI062`) to alawys provide an autofix ([#14188](https://github.com/astral-sh/ruff/pull/14188))
|
||||
- \[`pyflakes`\] Detect items that hash to same value in duplicate dictionaries (`F601`) ([#14065](https://github.com/astral-sh/ruff/pull/14065))
|
||||
- \[`ruff`\] Fix false positive for decorators (`RUF028`) ([#14061](https://github.com/astral-sh/ruff/pull/14061))
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Avoid parsing joint rule codes as distinct codes in `# noqa` ([#12809](https://github.com/astral-sh/ruff/pull/12809))
|
||||
- \[`eradicate`\] ignore `# language=` in commented-out-code rule (ERA001) ([#14069](https://github.com/astral-sh/ruff/pull/14069))
|
||||
- \[`flake8-bugbear`\] - do not run `mutable-argument-default` on stubs (`B006`) ([#14058](https://github.com/astral-sh/ruff/pull/14058))
|
||||
- \[`flake8-builtins`\] Skip lambda expressions in `builtin-argument-shadowing (A002)` ([#14144](https://github.com/astral-sh/ruff/pull/14144))
|
||||
- \[`flake8-comprehension`\] Also remove trailing comma while fixing `C409` and `C419` ([#14097](https://github.com/astral-sh/ruff/pull/14097))
|
||||
- \[`flake8-simplify`\] Allow `open` without context manager in `return` statement (`SIM115`) ([#14066](https://github.com/astral-sh/ruff/pull/14066))
|
||||
- \[`pylint`\] Respect hash-equivalent literals in `iteration-over-set` (`PLC0208`) ([#14063](https://github.com/astral-sh/ruff/pull/14063))
|
||||
- \[`pylint`\] Update known dunder methods for Python 3.13 (`PLW3201`) ([#14146](https://github.com/astral-sh/ruff/pull/14146))
|
||||
- \[`pyupgrade`\] - ignore kwarg unpacking for `UP044` ([#14053](https://github.com/astral-sh/ruff/pull/14053))
|
||||
- \[`refurb`\] Parse more exotic decimal strings in `verbose-decimal-constructor` (`FURB157`) ([#14098](https://github.com/astral-sh/ruff/pull/14098))
|
||||
|
||||
### Documentation
|
||||
|
||||
- Add links to missing related options within rule documentations ([#13971](https://github.com/astral-sh/ruff/pull/13971))
|
||||
- Add rule short code to mkdocs tags to allow searching via rule codes ([#14040](https://github.com/astral-sh/ruff/pull/14040))
|
||||
|
||||
## 0.7.2
|
||||
|
||||
### Preview features
|
||||
|
||||
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -2220,7 +2220,6 @@ dependencies = [
|
||||
"ruff_cache",
|
||||
"ruff_db",
|
||||
"ruff_python_ast",
|
||||
"ruff_text_size",
|
||||
"rustc-hash 2.0.0",
|
||||
"salsa",
|
||||
"tempfile",
|
||||
@@ -2317,7 +2316,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argfile",
|
||||
@@ -2432,7 +2431,6 @@ dependencies = [
|
||||
"salsa",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-tree",
|
||||
@@ -2534,7 +2532,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_linter"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"annotate-snippets 0.9.2",
|
||||
@@ -2849,7 +2847,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ruff_wasm"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"console_log",
|
||||
|
||||
@@ -136,8 +136,8 @@ curl -LsSf https://astral.sh/ruff/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/install.ps1 | iex"
|
||||
|
||||
# For a specific version.
|
||||
curl -LsSf https://astral.sh/ruff/0.7.3/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.7.3/install.ps1 | iex"
|
||||
curl -LsSf https://astral.sh/ruff/0.7.2/install.sh | sh
|
||||
powershell -c "irm https://astral.sh/ruff/0.7.2/install.ps1 | iex"
|
||||
```
|
||||
|
||||
You can also install Ruff via [Homebrew](https://formulae.brew.sh/formula/ruff), [Conda](https://anaconda.org/conda-forge/ruff),
|
||||
@@ -170,7 +170,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
|
||||
```yaml
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: v0.7.3
|
||||
rev: v0.7.2
|
||||
hooks:
|
||||
# Run the linter.
|
||||
- id: ruff
|
||||
|
||||
@@ -5,6 +5,9 @@ use anyhow::{anyhow, Context};
|
||||
use clap::Parser;
|
||||
use colored::Colorize;
|
||||
use crossbeam::channel as crossbeam_channel;
|
||||
use ruff_db::diagnostic::CompileDiagnostic;
|
||||
use salsa::plumbing::ZalsaDatabase;
|
||||
|
||||
use red_knot_python_semantic::SitePackages;
|
||||
use red_knot_server::run_server;
|
||||
use red_knot_workspace::db::RootDatabase;
|
||||
@@ -12,9 +15,7 @@ use red_knot_workspace::watch;
|
||||
use red_knot_workspace::watch::WorkspaceWatcher;
|
||||
use red_knot_workspace::workspace::settings::Configuration;
|
||||
use red_knot_workspace::workspace::WorkspaceMetadata;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::system::{OsSystem, System, SystemPath, SystemPathBuf};
|
||||
use salsa::plumbing::ZalsaDatabase;
|
||||
use target_version::TargetVersion;
|
||||
|
||||
use crate::logging::{setup_tracing, Verbosity};
|
||||
@@ -380,7 +381,7 @@ impl MainLoopCancellationToken {
|
||||
enum MainLoopMessage {
|
||||
CheckWorkspace,
|
||||
CheckCompleted {
|
||||
result: Vec<Box<dyn Diagnostic>>,
|
||||
result: Vec<CompileDiagnostic>,
|
||||
revision: u64,
|
||||
},
|
||||
ApplyChanges(Vec<watch::ChangeEvent>),
|
||||
|
||||
@@ -18,58 +18,3 @@ class Unit: ...
|
||||
b = Unit()(3.0) # error: "Object of type `Unit` is not callable"
|
||||
reveal_type(b) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Possibly unbound `__call__` method
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
class PossiblyNotCallable:
|
||||
if flag():
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
a = PossiblyNotCallable()
|
||||
result = a() # error: "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
|
||||
reveal_type(result) # revealed: int
|
||||
```
|
||||
|
||||
## Possibly unbound callable
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
if flag():
|
||||
class PossiblyUnbound:
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
a = PossiblyUnbound()
|
||||
reveal_type(a()) # revealed: int
|
||||
```
|
||||
|
||||
## Non-callable `__call__`
|
||||
|
||||
```py
|
||||
class NonCallable:
|
||||
__call__ = 1
|
||||
|
||||
a = NonCallable()
|
||||
# error: "Object of type `NonCallable` is not callable"
|
||||
reveal_type(a()) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Possibly non-callable `__call__`
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
class NonCallable:
|
||||
if flag():
|
||||
__call__ = 1
|
||||
else:
|
||||
def __call__(self) -> int: ...
|
||||
|
||||
a = NonCallable()
|
||||
# error: "Object of type `Literal[1] | Literal[__call__]` is not callable (due to union element `Literal[1]`)"
|
||||
reveal_type(a()) # revealed: Unknown | int
|
||||
```
|
||||
|
||||
@@ -44,16 +44,3 @@ reveal_type(bar()) # revealed: @Todo
|
||||
nonsense = 123
|
||||
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
|
||||
```
|
||||
|
||||
## Potentially unbound function
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
if flag():
|
||||
def foo() -> int:
|
||||
return 42
|
||||
|
||||
# error: [possibly-unresolved-reference]
|
||||
reveal_type(foo()) # revealed: int
|
||||
```
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
# Comparison: Intersections
|
||||
|
||||
## Positive contributions
|
||||
|
||||
If we have an intersection type `A & B` and we get a definitive true/false answer for one of the
|
||||
types, we can infer that the result for the intersection type is also true/false:
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
|
||||
class Child1(Base):
|
||||
def __eq__(self, other) -> Literal[True]:
|
||||
return True
|
||||
|
||||
class Child2(Base): ...
|
||||
|
||||
def get_base() -> Base: ...
|
||||
|
||||
x = get_base()
|
||||
c1 = Child1()
|
||||
|
||||
# Create an intersection type through narrowing:
|
||||
if isinstance(x, Child1):
|
||||
if isinstance(x, Child2):
|
||||
reveal_type(x) # revealed: Child1 & Child2
|
||||
|
||||
reveal_type(x == 1) # revealed: Literal[True]
|
||||
|
||||
# Other comparison operators fall back to the base type:
|
||||
reveal_type(x > 1) # revealed: bool
|
||||
reveal_type(x is c1) # revealed: bool
|
||||
```
|
||||
|
||||
## Negative contributions
|
||||
|
||||
Negative contributions to the intersection type only allow simplifications in a few special cases
|
||||
(equality and identity comparisons).
|
||||
|
||||
### Equality comparisons
|
||||
|
||||
#### Literal strings
|
||||
|
||||
```py
|
||||
x = "x" * 1_000_000_000
|
||||
y = "y" * 1_000_000_000
|
||||
reveal_type(x) # revealed: LiteralString
|
||||
|
||||
if x != "abc":
|
||||
reveal_type(x) # revealed: LiteralString & ~Literal["abc"]
|
||||
|
||||
reveal_type(x == "abc") # revealed: Literal[False]
|
||||
reveal_type("abc" == x) # revealed: Literal[False]
|
||||
reveal_type(x == "something else") # revealed: bool
|
||||
reveal_type("something else" == x) # revealed: bool
|
||||
|
||||
reveal_type(x != "abc") # revealed: Literal[True]
|
||||
reveal_type("abc" != x) # revealed: Literal[True]
|
||||
reveal_type(x != "something else") # revealed: bool
|
||||
reveal_type("something else" != x) # revealed: bool
|
||||
|
||||
reveal_type(x == y) # revealed: bool
|
||||
reveal_type(y == x) # revealed: bool
|
||||
reveal_type(x != y) # revealed: bool
|
||||
reveal_type(y != x) # revealed: bool
|
||||
|
||||
reveal_type(x >= "abc") # revealed: bool
|
||||
reveal_type("abc" >= x) # revealed: bool
|
||||
|
||||
reveal_type(x in "abc") # revealed: bool
|
||||
reveal_type("abc" in x) # revealed: bool
|
||||
```
|
||||
|
||||
#### Integers
|
||||
|
||||
```py
|
||||
def get_int() -> int: ...
|
||||
|
||||
x = get_int()
|
||||
|
||||
if x != 1:
|
||||
reveal_type(x) # revealed: int & ~Literal[1]
|
||||
|
||||
reveal_type(x != 1) # revealed: Literal[True]
|
||||
reveal_type(x != 2) # revealed: bool
|
||||
|
||||
reveal_type(x == 1) # revealed: Literal[False]
|
||||
reveal_type(x == 2) # revealed: bool
|
||||
```
|
||||
|
||||
### Identity comparisons
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
o = object()
|
||||
|
||||
a = A()
|
||||
n = None
|
||||
|
||||
if o is not None:
|
||||
reveal_type(o) # revealed: object & ~None
|
||||
|
||||
reveal_type(o is n) # revealed: Literal[False]
|
||||
reveal_type(o is not n) # revealed: Literal[True]
|
||||
```
|
||||
|
||||
## Diagnostics
|
||||
|
||||
### Unsupported operators for positive contributions
|
||||
|
||||
Raise an error if any of the positive contributions to the intersection type are unsupported for the
|
||||
given operator:
|
||||
|
||||
```py
|
||||
class Container:
|
||||
def __contains__(self, x) -> bool: ...
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
x = get_object()
|
||||
|
||||
if isinstance(x, Container):
|
||||
if isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & NonContainer
|
||||
|
||||
# error: [unsupported-operator] "Operator `in` is not supported for types `int` and `NonContainer`"
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
|
||||
### Unsupported operators for negative contributions
|
||||
|
||||
Do *not* raise an error if any of the negative contributions to the intersection type are
|
||||
unsupported for the given operator:
|
||||
|
||||
```py
|
||||
class Container:
|
||||
def __contains__(self, x) -> bool: ...
|
||||
|
||||
class NonContainer: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
x = get_object()
|
||||
|
||||
if isinstance(x, Container):
|
||||
if not isinstance(x, NonContainer):
|
||||
reveal_type(x) # revealed: Container & ~NonContainer
|
||||
|
||||
# No error here!
|
||||
reveal_type(2 in x) # revealed: bool
|
||||
```
|
||||
@@ -9,5 +9,4 @@ try:
|
||||
print
|
||||
except as e: # error: [invalid-syntax]
|
||||
reveal_type(e) # revealed: Unknown
|
||||
|
||||
```
|
||||
|
||||
@@ -238,7 +238,7 @@ class Test:
|
||||
def coinflip() -> bool:
|
||||
return True
|
||||
|
||||
# error: [not-iterable] "Object of type `Test | Literal[42]` is not iterable because its `__iter__` method is possibly unbound"
|
||||
# TODO: we should emit a diagnostic here (it might not be iterable)
|
||||
for x in Test() if coinflip() else 42:
|
||||
reveal_type(x) # revealed: int
|
||||
```
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
## Default
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
|
||||
reveal_type(M.__class__) # revealed: Literal[type]
|
||||
```
|
||||
|
||||
## `object`
|
||||
|
||||
```py
|
||||
reveal_type(object.__class__) # revealed: Literal[type]
|
||||
```
|
||||
|
||||
## `type`
|
||||
|
||||
```py
|
||||
reveal_type(type.__class__) # revealed: Literal[type]
|
||||
```
|
||||
|
||||
## Basic
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class B(metaclass=M): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Invalid metaclass
|
||||
|
||||
A class which doesn't inherit `type` (and/or doesn't implement a custom `__new__` accepting the same
|
||||
arguments as `type.__new__`) isn't a valid metaclass.
|
||||
|
||||
```py
|
||||
class M: ...
|
||||
class A(metaclass=M): ...
|
||||
|
||||
# TODO: emit a diagnostic for the invalid metaclass
|
||||
reveal_type(A.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Linear inheritance
|
||||
|
||||
If a class is a subclass of a class with a custom metaclass, then the subclass will also have that
|
||||
metaclass.
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(A): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Conflict (1)
|
||||
|
||||
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
|
||||
bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a
|
||||
subclass or the class itself.)
|
||||
|
||||
```py
|
||||
class M1(type): ...
|
||||
class M2(type): ...
|
||||
class A(metaclass=M1): ...
|
||||
class B(metaclass=M2): ...
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`C`) must be a subclass of the metaclasses of all its bases, but `M1` (metaclass of base class `A`) and `M2` (metaclass of base class `B`) have no subclass relationship"
|
||||
class C(A, B): ...
|
||||
|
||||
reveal_type(C.__class__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Conflict (2)
|
||||
|
||||
The metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its
|
||||
bases. ("Strict subclass" is a synonym for "proper subclass"; a non-strict subclass can be a
|
||||
subclass or the class itself.)
|
||||
|
||||
```py
|
||||
class M1(type): ...
|
||||
class M2(type): ...
|
||||
class A(metaclass=M1): ...
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`B`) must be a subclass of the metaclasses of all its bases, but `M2` (metaclass of `B`) and `M1` (metaclass of base class `A`) have no subclass relationship"
|
||||
class B(A, metaclass=M2): ...
|
||||
|
||||
reveal_type(B.__class__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Common metaclass
|
||||
|
||||
A class has two explicit bases, both of which have the same metaclass.
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(metaclass=M): ...
|
||||
class C(A, B): ...
|
||||
|
||||
reveal_type(C.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Metaclass metaclass
|
||||
|
||||
A class has an explicit base with a custom metaclass. That metaclass itself has a custom metaclass.
|
||||
|
||||
```py
|
||||
class M1(type): ...
|
||||
class M2(type, metaclass=M1): ...
|
||||
class M3(M2): ...
|
||||
class A(metaclass=M3): ...
|
||||
class B(A): ...
|
||||
|
||||
reveal_type(A.__class__) # revealed: Literal[M3]
|
||||
```
|
||||
|
||||
## Diamond inheritance
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class M1(M): ...
|
||||
class M2(M): ...
|
||||
class M12(M1, M2): ...
|
||||
class A(metaclass=M1): ...
|
||||
class B(metaclass=M2): ...
|
||||
class C(metaclass=M12): ...
|
||||
|
||||
# error: [conflicting-metaclass] "The metaclass of a derived class (`D`) must be a subclass of the metaclasses of all its bases, but `M1` (metaclass of base class `A`) and `M2` (metaclass of base class `B`) have no subclass relationship"
|
||||
class D(A, B, C): ...
|
||||
|
||||
reveal_type(D.__class__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## Unknown
|
||||
|
||||
```py
|
||||
from nonexistent_module import UnknownClass # error: [unresolved-import]
|
||||
|
||||
class C(UnknownClass): ...
|
||||
|
||||
# TODO: should be `type[type] & Unknown`
|
||||
reveal_type(C.__class__) # revealed: Literal[type]
|
||||
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(A, UnknownClass): ...
|
||||
|
||||
# TODO: should be `type[M] & Unknown`
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Duplicate
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A(metaclass=M): ...
|
||||
class B(A, A): ... # error: [duplicate-base] "Duplicate base class `A`"
|
||||
|
||||
reveal_type(B.__class__) # revealed: Literal[M]
|
||||
```
|
||||
|
||||
## Non-class
|
||||
|
||||
When a class has an explicit `metaclass` that is not a class, but is a callable that accepts
|
||||
`type.__new__` arguments, we should return the meta type of its return type.
|
||||
|
||||
```py
|
||||
def f(*args, **kwargs) -> int: ...
|
||||
|
||||
class A(metaclass=f): ...
|
||||
|
||||
# TODO should be `type[int]`
|
||||
reveal_type(A.__class__) # revealed: @Todo
|
||||
```
|
||||
|
||||
## Cyclic
|
||||
|
||||
Retrieving the metaclass of a cyclically defined class should not cause an infinite loop.
|
||||
|
||||
```py path=a.pyi
|
||||
class A(B): ... # error: [cyclic-class-def]
|
||||
class B(C): ... # error: [cyclic-class-def]
|
||||
class C(A): ... # error: [cyclic-class-def]
|
||||
|
||||
reveal_type(A.__class__) # revealed: Unknown
|
||||
```
|
||||
|
||||
## PEP 695 generic
|
||||
|
||||
```py
|
||||
class M(type): ...
|
||||
class A[T: str](metaclass=M): ...
|
||||
|
||||
reveal_type(A.__class__) # revealed: Literal[M]
|
||||
```
|
||||
@@ -1,244 +0,0 @@
|
||||
# Narrowing for `issubclass` checks
|
||||
|
||||
Narrowing for `issubclass(class, classinfo)` expressions.
|
||||
|
||||
## `classinfo` is a single type
|
||||
|
||||
### Basic example
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
if issubclass(t, bytes):
|
||||
reveal_type(t) # revealed: Never
|
||||
|
||||
if issubclass(t, object):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
|
||||
if issubclass(t, str):
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Never
|
||||
```
|
||||
|
||||
### Proper narrowing in `elif` and `else` branches
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str if flag() else bytes
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str, bytes]
|
||||
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
elif issubclass(t, str):
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[bytes]
|
||||
```
|
||||
|
||||
### Multiple derived classes
|
||||
|
||||
```py
|
||||
class Base: ...
|
||||
class Derived1(Base): ...
|
||||
class Derived2(Base): ...
|
||||
class Unrelated: ...
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t1 = Derived1 if flag() else Derived2
|
||||
|
||||
if issubclass(t1, Base):
|
||||
reveal_type(t1) # revealed: Literal[Derived1, Derived2]
|
||||
|
||||
if issubclass(t1, Derived1):
|
||||
reveal_type(t1) # revealed: Literal[Derived1]
|
||||
else:
|
||||
reveal_type(t1) # revealed: Literal[Derived2]
|
||||
|
||||
t2 = Derived1 if flag() else Base
|
||||
|
||||
if issubclass(t2, Base):
|
||||
reveal_type(t2) # revealed: Literal[Derived1, Base]
|
||||
|
||||
t3 = Derived1 if flag() else Unrelated
|
||||
|
||||
if issubclass(t3, Base):
|
||||
reveal_type(t3) # revealed: Literal[Derived1]
|
||||
else:
|
||||
reveal_type(t3) # revealed: Literal[Unrelated]
|
||||
```
|
||||
|
||||
### Narrowing for non-literals
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
class B: ...
|
||||
|
||||
def get_class() -> type[object]: ...
|
||||
|
||||
t = get_class()
|
||||
|
||||
if issubclass(t, A):
|
||||
reveal_type(t) # revealed: type[A]
|
||||
if issubclass(t, B):
|
||||
reveal_type(t) # revealed: type[A] & type[B]
|
||||
else:
|
||||
reveal_type(t) # revealed: type[object] & ~type[A]
|
||||
```
|
||||
|
||||
### Handling of `None`
|
||||
|
||||
```py
|
||||
from types import NoneType
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else NoneType
|
||||
|
||||
if issubclass(t, NoneType):
|
||||
reveal_type(t) # revealed: Literal[NoneType]
|
||||
|
||||
if issubclass(t, type(None)):
|
||||
# TODO: this should be just `Literal[NoneType]`
|
||||
reveal_type(t) # revealed: Literal[int, NoneType]
|
||||
```
|
||||
|
||||
## `classinfo` contains multiple types
|
||||
|
||||
### (Nested) tuples of types
|
||||
|
||||
```py
|
||||
class Unrelated: ...
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str if flag() else bytes
|
||||
|
||||
if issubclass(t, (int, (Unrelated, (bytes,)))):
|
||||
reveal_type(t) # revealed: Literal[int, bytes]
|
||||
else:
|
||||
reveal_type(t) # revealed: Literal[str]
|
||||
```
|
||||
|
||||
## Special cases
|
||||
|
||||
### Emit a diagnostic if the first argument is of wrong type
|
||||
|
||||
#### Too wide
|
||||
|
||||
`type[object]` is a subtype of `object`, but not every `object` can be passed as the first argument
|
||||
to `issubclass`:
|
||||
|
||||
```py
|
||||
class A: ...
|
||||
|
||||
def get_object() -> object: ...
|
||||
|
||||
t = get_object()
|
||||
|
||||
# TODO: we should emit a diagnostic here
|
||||
if issubclass(t, A):
|
||||
reveal_type(t) # revealed: type[A]
|
||||
```
|
||||
|
||||
#### Wrong
|
||||
|
||||
`Literal[1]` and `type` are entirely disjoint, so the inferred type of `Literal[1] & type[int]` is
|
||||
eagerly simplified to `Never` as a result of the type narrowing in the `if issubclass(t, int)`
|
||||
branch:
|
||||
|
||||
```py
|
||||
t = 1
|
||||
|
||||
# TODO: we should emit a diagnostic here
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Never
|
||||
```
|
||||
|
||||
### Do not use custom `issubclass` for narrowing
|
||||
|
||||
```py
|
||||
def issubclass(c, ci):
|
||||
return True
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
if issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
```
|
||||
|
||||
### Do support narrowing if `issubclass` is aliased
|
||||
|
||||
```py
|
||||
issubclass_alias = issubclass
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
if issubclass_alias(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
```
|
||||
|
||||
### Do support narrowing if `issubclass` is imported
|
||||
|
||||
```py
|
||||
from builtins import issubclass as imported_issubclass
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
if imported_issubclass(t, int):
|
||||
reveal_type(t) # revealed: Literal[int]
|
||||
```
|
||||
|
||||
### Do not narrow if second argument is not a proper `classinfo` argument
|
||||
|
||||
```py
|
||||
from typing import Any
|
||||
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if issubclass(t, "str"):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if issubclass(t, (bytes, "str")):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic during
|
||||
# type checking
|
||||
if issubclass(t, Any):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
```
|
||||
|
||||
### Do not narrow if there are keyword arguments
|
||||
|
||||
```py
|
||||
def flag() -> bool: ...
|
||||
|
||||
t = int if flag() else str
|
||||
|
||||
# TODO: this should cause us to emit a diagnostic
|
||||
# (`issubclass` has no `foo` parameter)
|
||||
if issubclass(t, int, foo="bar"):
|
||||
reveal_type(t) # revealed: Literal[int, str]
|
||||
```
|
||||
@@ -60,7 +60,8 @@ reveal_type(typing.__init__) # revealed: Literal[__init__]
|
||||
# These come from `builtins.object`, not `types.ModuleType`:
|
||||
reveal_type(typing.__eq__) # revealed: Literal[__eq__]
|
||||
|
||||
reveal_type(typing.__class__) # revealed: Literal[type]
|
||||
# TODO: understand properties
|
||||
reveal_type(typing.__class__) # revealed: Literal[__class__]
|
||||
|
||||
# TODO: needs support for attribute access on instances, properties and generics;
|
||||
# should be `dict[str, Any]`
|
||||
|
||||
@@ -281,12 +281,8 @@ impl<'db> SemanticIndexBuilder<'db> {
|
||||
debug_assert!(popped_assignment.is_some());
|
||||
}
|
||||
|
||||
fn current_assignment(&self) -> Option<CurrentAssignment<'db>> {
|
||||
self.current_assignments.last().copied()
|
||||
}
|
||||
|
||||
fn current_assignment_mut(&mut self) -> Option<&mut CurrentAssignment<'db>> {
|
||||
self.current_assignments.last_mut()
|
||||
fn current_assignment(&self) -> Option<&CurrentAssignment<'db>> {
|
||||
self.current_assignments.last()
|
||||
}
|
||||
|
||||
fn add_pattern_constraint(
|
||||
@@ -631,7 +627,6 @@ where
|
||||
ast::Expr::List(_) | ast::Expr::Tuple(_) => {
|
||||
Some(CurrentAssignment::Assign {
|
||||
node,
|
||||
first: true,
|
||||
unpack: Some(Unpack::new(
|
||||
self.db,
|
||||
self.file,
|
||||
@@ -645,11 +640,9 @@ where
|
||||
)),
|
||||
})
|
||||
}
|
||||
ast::Expr::Name(_) => Some(CurrentAssignment::Assign {
|
||||
node,
|
||||
unpack: None,
|
||||
first: false,
|
||||
}),
|
||||
ast::Expr::Name(_) => {
|
||||
Some(CurrentAssignment::Assign { node, unpack: None })
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -994,19 +987,14 @@ where
|
||||
}
|
||||
|
||||
if is_definition {
|
||||
match self.current_assignment() {
|
||||
Some(CurrentAssignment::Assign {
|
||||
node,
|
||||
first,
|
||||
unpack,
|
||||
}) => {
|
||||
match self.current_assignment().copied() {
|
||||
Some(CurrentAssignment::Assign { node, unpack }) => {
|
||||
self.add_definition(
|
||||
symbol,
|
||||
AssignmentDefinitionNodeRef {
|
||||
unpack,
|
||||
value: &node.value,
|
||||
name: name_node,
|
||||
first,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1057,11 +1045,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(CurrentAssignment::Assign { first, .. }) = self.current_assignment_mut()
|
||||
{
|
||||
*first = false;
|
||||
}
|
||||
|
||||
walk_expr(self, expr);
|
||||
}
|
||||
ast::Expr::Named(node) => {
|
||||
@@ -1262,7 +1245,6 @@ where
|
||||
enum CurrentAssignment<'a> {
|
||||
Assign {
|
||||
node: &'a ast::StmtAssign,
|
||||
first: bool,
|
||||
unpack: Option<Unpack<'a>>,
|
||||
},
|
||||
AnnAssign(&'a ast::StmtAnnAssign),
|
||||
|
||||
@@ -183,7 +183,6 @@ pub(crate) struct AssignmentDefinitionNodeRef<'a> {
|
||||
pub(crate) unpack: Option<Unpack<'a>>,
|
||||
pub(crate) value: &'a ast::Expr,
|
||||
pub(crate) name: &'a ast::ExprName,
|
||||
pub(crate) first: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -251,12 +250,10 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
unpack,
|
||||
value,
|
||||
name,
|
||||
first,
|
||||
}) => DefinitionKind::Assignment(AssignmentDefinitionKind {
|
||||
target: TargetKind::from(unpack),
|
||||
value: AstNodeRef::new(parsed.clone(), value),
|
||||
name: AstNodeRef::new(parsed, name),
|
||||
first,
|
||||
}),
|
||||
DefinitionNodeRef::AnnotatedAssignment(assign) => {
|
||||
DefinitionKind::AnnotatedAssignment(AstNodeRef::new(parsed, assign))
|
||||
@@ -333,7 +330,6 @@ impl<'db> DefinitionNodeRef<'db> {
|
||||
value: _,
|
||||
unpack: _,
|
||||
name,
|
||||
first: _,
|
||||
}) => name.into(),
|
||||
Self::AnnotatedAssignment(node) => node.into(),
|
||||
Self::AugmentedAssignment(node) => node.into(),
|
||||
@@ -539,11 +535,10 @@ pub struct AssignmentDefinitionKind<'db> {
|
||||
target: TargetKind<'db>,
|
||||
value: AstNodeRef<ast::Expr>,
|
||||
name: AstNodeRef<ast::ExprName>,
|
||||
first: bool,
|
||||
}
|
||||
|
||||
impl<'db> AssignmentDefinitionKind<'db> {
|
||||
pub(crate) fn target(&self) -> TargetKind<'db> {
|
||||
impl AssignmentDefinitionKind<'_> {
|
||||
pub(crate) fn target(&self) -> TargetKind {
|
||||
self.target
|
||||
}
|
||||
|
||||
@@ -554,10 +549,6 @@ impl<'db> AssignmentDefinitionKind<'db> {
|
||||
pub(crate) fn name(&self) -> &ast::ExprName {
|
||||
self.name.node()
|
||||
}
|
||||
|
||||
pub(crate) fn is_first(&self) -> bool {
|
||||
self.first
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::{
|
||||
Db,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum Boundness {
|
||||
Bound,
|
||||
MayBeUnbound,
|
||||
@@ -44,13 +44,17 @@ impl<'db> Symbol<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
|
||||
pub(crate) fn unwrap_or(&self, other: Type<'db>) -> Type<'db> {
|
||||
match self {
|
||||
Symbol::Type(ty, _) => *ty,
|
||||
Symbol::Unbound => Type::Unknown,
|
||||
Symbol::Unbound => other,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unwrap_or_unknown(&self) -> Type<'db> {
|
||||
self.unwrap_or(Type::Unknown)
|
||||
}
|
||||
|
||||
pub(crate) fn as_type(&self) -> Option<Type<'db>> {
|
||||
match self {
|
||||
Symbol::Type(ty, _) => Some(*ty),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -246,7 +246,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
}
|
||||
} else {
|
||||
// ~Literal[True] & bool = Literal[False]
|
||||
if let Type::Instance(InstanceType { class }) = new_positive {
|
||||
if let Type::Instance(InstanceType { class, .. }) = new_positive {
|
||||
if class.is_known(db, KnownClass::Bool) {
|
||||
if let Some(&Type::BooleanLiteral(value)) = self
|
||||
.negative
|
||||
@@ -317,7 +317,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||
// Adding any of these types to the negative side of an intersection
|
||||
// is equivalent to adding it to the positive side. We do this to
|
||||
// simplify the representation.
|
||||
self.add_positive(db, ty);
|
||||
self.positive.insert(ty);
|
||||
}
|
||||
// ~Literal[True] & bool = Literal[False]
|
||||
Type::BooleanLiteral(bool)
|
||||
@@ -592,22 +592,6 @@ mod tests {
|
||||
assert_eq!(ta_not_i0.display(&db).to_string(), "int & Any | Literal[1]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_intersection_simplify_negative_any() {
|
||||
let db = setup_db();
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_negative(Type::Any)
|
||||
.build();
|
||||
assert_eq!(ty, Type::Any);
|
||||
|
||||
let ty = IntersectionBuilder::new(&db)
|
||||
.add_positive(Type::Never)
|
||||
.add_negative(Type::Any)
|
||||
.build();
|
||||
assert_eq!(ty, Type::Never);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_distributes_over_union() {
|
||||
let db = setup_db();
|
||||
|
||||
@@ -1,15 +1,199 @@
|
||||
use crate::types::{ClassLiteralType, Type};
|
||||
use crate::Db;
|
||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
||||
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic, Severity};
|
||||
use ruff_db::files::File;
|
||||
use ruff_python_ast::{self as ast, AnyNodeRef};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
||||
use crate::types::{ClassLiteralType, Type};
|
||||
use crate::Db;
|
||||
|
||||
/// Returns `true` if any diagnostic is enabled for this file.
|
||||
pub(crate) fn is_any_diagnostic_enabled(db: &dyn Db, file: File) -> bool {
|
||||
db.is_file_open(file)
|
||||
}
|
||||
|
||||
pub(crate) fn report_type_diagnostic(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
node: AnyNodeRef,
|
||||
rule: &str,
|
||||
message: std::fmt::Arguments,
|
||||
) {
|
||||
if !is_any_diagnostic_enabled(db, file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Don't emit the diagnostic if:
|
||||
// * The enclosing node contains any syntax errors
|
||||
// * The rule is disabled for this file. We probably want to introduce a new query that
|
||||
// returns a rule selector for a given file that respects the package's settings,
|
||||
// any global pragma comments in the file, and any per-file-ignores.
|
||||
|
||||
CompileDiagnostic::report(
|
||||
db.upcast(),
|
||||
TypeCheckDiagnostic {
|
||||
file,
|
||||
rule: rule.to_string(),
|
||||
message: message.to_string(),
|
||||
range: node.range(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
pub(super) fn report_not_iterable(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
node: AnyNodeRef,
|
||||
not_iterable_ty: Type,
|
||||
) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"not-iterable",
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable",
|
||||
not_iterable_ty.display(db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
|
||||
pub(super) fn report_index_out_of_bounds(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
kind: &'static str,
|
||||
node: AnyNodeRef,
|
||||
tuple_ty: Type,
|
||||
length: usize,
|
||||
index: i64,
|
||||
) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"index-out-of-bounds",
|
||||
format_args!(
|
||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||
tuple_ty.display(db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that a type does not support subscripting.
|
||||
pub(super) fn report_non_subscriptable(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
node: AnyNodeRef,
|
||||
non_subscriptable_ty: Type,
|
||||
method: &str,
|
||||
) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"non-subscriptable",
|
||||
format_args!(
|
||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||
non_subscriptable_ty.display(db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn report_unresolved_module<'a>(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
import_node: impl Into<AnyNodeRef<'a>>,
|
||||
level: u32,
|
||||
module: Option<&str>,
|
||||
) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
import_node.into(),
|
||||
"unresolved-import",
|
||||
format_args!(
|
||||
"Cannot resolve import `{}{}`",
|
||||
".".repeat(level as usize),
|
||||
module.unwrap_or_default()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn report_slice_step_size_zero(db: &dyn Db, file: File, node: AnyNodeRef) {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"zero-stepsize-in-slice",
|
||||
format_args!("Slice step size can not be zero"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn report_invalid_assignment(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
node: AnyNodeRef,
|
||||
declared_ty: Type,
|
||||
assigned_ty: Type,
|
||||
) {
|
||||
match declared_ty {
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
report_type_diagnostic(db, file, node, "invalid-assignment", format_args!(
|
||||
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
||||
class.name(db)));
|
||||
}
|
||||
Type::FunctionLiteral(function) => {
|
||||
report_type_diagnostic(db, file, node, "invalid-assignment", format_args!(
|
||||
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||
function.name(db)));
|
||||
}
|
||||
_ => {
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
node,
|
||||
"invalid-assignment",
|
||||
format_args!(
|
||||
"Object of type `{}` is not assignable to `{}`",
|
||||
assigned_ty.display(db),
|
||||
declared_ty.display(db),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn report_possibly_unresolved_reference(
|
||||
db: &dyn Db,
|
||||
file: File,
|
||||
expr_name_node: &ast::ExprName,
|
||||
) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
expr_name_node.into(),
|
||||
"possibly-unresolved-reference",
|
||||
format_args!("Name `{id}` used when possibly not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn report_unresolved_reference(db: &dyn Db, file: File, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
report_type_diagnostic(
|
||||
db,
|
||||
file,
|
||||
expr_name_node.into(),
|
||||
"unresolved-reference",
|
||||
format_args!("Name `{id}` used when not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct TypeCheckDiagnostic {
|
||||
// TODO: Don't use string keys for rules
|
||||
pub(super) rule: String,
|
||||
@@ -23,304 +207,29 @@ impl TypeCheckDiagnostic {
|
||||
&self.rule
|
||||
}
|
||||
|
||||
pub fn message(&self) -> &str {
|
||||
&self.message
|
||||
}
|
||||
|
||||
pub fn file(&self) -> File {
|
||||
self.file
|
||||
pub fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for TypeCheckDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
TypeCheckDiagnostic::rule(self)
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
TypeCheckDiagnostic::message(self).into()
|
||||
fn message(&self) -> std::borrow::Cow<str> {
|
||||
Cow::Borrowed(&self.message)
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
TypeCheckDiagnostic::file(self)
|
||||
self.file
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
Some(Ranged::range(self))
|
||||
Some(self.range)
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Error
|
||||
}
|
||||
}
|
||||
|
||||
impl Ranged for TypeCheckDiagnostic {
|
||||
fn range(&self) -> TextRange {
|
||||
self.range
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of type check diagnostics.
|
||||
///
|
||||
/// The diagnostics are wrapped in an `Arc` because they need to be cloned multiple times
|
||||
/// when going from `infer_expression` to `check_file`. We could consider
|
||||
/// making [`TypeCheckDiagnostic`] a Salsa struct to have them Arena-allocated (once the Tables refactor is done).
|
||||
/// Using Salsa struct does have the downside that it leaks the Salsa dependency into diagnostics and
|
||||
/// each Salsa-struct comes with an overhead.
|
||||
#[derive(Default, Eq, PartialEq)]
|
||||
pub struct TypeCheckDiagnostics {
|
||||
inner: Vec<std::sync::Arc<TypeCheckDiagnostic>>,
|
||||
}
|
||||
|
||||
impl TypeCheckDiagnostics {
|
||||
pub fn new() -> Self {
|
||||
Self { inner: Vec::new() }
|
||||
}
|
||||
|
||||
pub(super) fn push(&mut self, diagnostic: TypeCheckDiagnostic) {
|
||||
self.inner.push(Arc::new(diagnostic));
|
||||
}
|
||||
|
||||
pub(crate) fn shrink_to_fit(&mut self) {
|
||||
self.inner.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<TypeCheckDiagnostic> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = TypeCheckDiagnostic>>(&mut self, iter: T) {
|
||||
self.inner.extend(iter.into_iter().map(std::sync::Arc::new));
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
|
||||
self.inner.extend(iter);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Extend<&'a std::sync::Arc<TypeCheckDiagnostic>> for TypeCheckDiagnostics {
|
||||
fn extend<T: IntoIterator<Item = &'a Arc<TypeCheckDiagnostic>>>(&mut self, iter: T) {
|
||||
self.inner
|
||||
.extend(iter.into_iter().map(std::sync::Arc::clone));
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for TypeCheckDiagnostics {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.inner.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TypeCheckDiagnostics {
|
||||
type Target = [std::sync::Arc<TypeCheckDiagnostic>];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for TypeCheckDiagnostics {
|
||||
type Item = Arc<TypeCheckDiagnostic>;
|
||||
type IntoIter = std::vec::IntoIter<std::sync::Arc<TypeCheckDiagnostic>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.inner.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a TypeCheckDiagnostics {
|
||||
type Item = &'a Arc<TypeCheckDiagnostic>;
|
||||
type IntoIter = std::slice::Iter<'a, std::sync::Arc<TypeCheckDiagnostic>>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.inner.iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct TypeCheckDiagnosticsBuilder<'db> {
|
||||
db: &'db dyn Db,
|
||||
file: File,
|
||||
diagnostics: TypeCheckDiagnostics,
|
||||
}
|
||||
|
||||
impl<'db> TypeCheckDiagnosticsBuilder<'db> {
|
||||
pub(super) fn new(db: &'db dyn Db, file: File) -> Self {
|
||||
Self {
|
||||
db,
|
||||
file,
|
||||
diagnostics: TypeCheckDiagnostics::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
pub(super) fn add_not_iterable(&mut self, node: AnyNodeRef, not_iterable_ty: Type<'db>) {
|
||||
self.add(
|
||||
node,
|
||||
"not-iterable",
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable",
|
||||
not_iterable_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that the object represented by `node` is not iterable
|
||||
/// because its `__iter__` method is possibly unbound.
|
||||
pub(super) fn add_not_iterable_possibly_unbound(
|
||||
&mut self,
|
||||
node: AnyNodeRef,
|
||||
element_ty: Type<'db>,
|
||||
) {
|
||||
self.add(
|
||||
node,
|
||||
"not-iterable",
|
||||
format_args!(
|
||||
"Object of type `{}` is not iterable because its `__iter__` method is possibly unbound",
|
||||
element_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that an index is out of bounds for a tuple.
|
||||
pub(super) fn add_index_out_of_bounds(
|
||||
&mut self,
|
||||
kind: &'static str,
|
||||
node: AnyNodeRef,
|
||||
tuple_ty: Type<'db>,
|
||||
length: usize,
|
||||
index: i64,
|
||||
) {
|
||||
self.add(
|
||||
node,
|
||||
"index-out-of-bounds",
|
||||
format_args!(
|
||||
"Index {index} is out of bounds for {kind} `{}` with length {length}",
|
||||
tuple_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Emit a diagnostic declaring that a type does not support subscripting.
|
||||
pub(super) fn add_non_subscriptable(
|
||||
&mut self,
|
||||
node: AnyNodeRef,
|
||||
non_subscriptable_ty: Type<'db>,
|
||||
method: &str,
|
||||
) {
|
||||
self.add(
|
||||
node,
|
||||
"non-subscriptable",
|
||||
format_args!(
|
||||
"Cannot subscript object of type `{}` with no `{method}` method",
|
||||
non_subscriptable_ty.display(self.db)
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_unresolved_module(
|
||||
&mut self,
|
||||
import_node: impl Into<AnyNodeRef<'db>>,
|
||||
level: u32,
|
||||
module: Option<&str>,
|
||||
) {
|
||||
self.add(
|
||||
import_node.into(),
|
||||
"unresolved-import",
|
||||
format_args!(
|
||||
"Cannot resolve import `{}{}`",
|
||||
".".repeat(level as usize),
|
||||
module.unwrap_or_default()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_slice_step_size_zero(&mut self, node: AnyNodeRef) {
|
||||
self.add(
|
||||
node,
|
||||
"zero-stepsize-in-slice",
|
||||
format_args!("Slice step size can not be zero"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_invalid_assignment(
|
||||
&mut self,
|
||||
node: AnyNodeRef,
|
||||
declared_ty: Type<'db>,
|
||||
assigned_ty: Type<'db>,
|
||||
) {
|
||||
match declared_ty {
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => {
|
||||
self.add(node, "invalid-assignment", format_args!(
|
||||
"Implicit shadowing of class `{}`; annotate to make it explicit if this is intentional",
|
||||
class.name(self.db)));
|
||||
}
|
||||
Type::FunctionLiteral(function) => {
|
||||
self.add(node, "invalid-assignment", format_args!(
|
||||
"Implicit shadowing of function `{}`; annotate to make it explicit if this is intentional",
|
||||
function.name(self.db)));
|
||||
}
|
||||
_ => {
|
||||
self.add(
|
||||
node,
|
||||
"invalid-assignment",
|
||||
format_args!(
|
||||
"Object of type `{}` is not assignable to `{}`",
|
||||
assigned_ty.display(self.db),
|
||||
declared_ty.display(self.db),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn add_possibly_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
self.add(
|
||||
expr_name_node.into(),
|
||||
"possibly-unresolved-reference",
|
||||
format_args!("Name `{id}` used when possibly not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_unresolved_reference(&mut self, expr_name_node: &ast::ExprName) {
|
||||
let ast::ExprName { id, .. } = expr_name_node;
|
||||
|
||||
self.add(
|
||||
expr_name_node.into(),
|
||||
"unresolved-reference",
|
||||
format_args!("Name `{id}` used when not defined"),
|
||||
);
|
||||
}
|
||||
|
||||
/// Adds a new diagnostic.
|
||||
///
|
||||
/// The diagnostic does not get added if the rule isn't enabled for this file.
|
||||
pub(super) fn add(&mut self, node: AnyNodeRef, rule: &str, message: std::fmt::Arguments) {
|
||||
if !self.db.is_file_open(self.file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Don't emit the diagnostic if:
|
||||
// * The enclosing node contains any syntax errors
|
||||
// * The rule is disabled for this file. We probably want to introduce a new query that
|
||||
// returns a rule selector for a given file that respects the package's settings,
|
||||
// any global pragma comments in the file, and any per-file-ignores.
|
||||
|
||||
self.diagnostics.push(TypeCheckDiagnostic {
|
||||
file: self.file,
|
||||
rule: rule.to_string(),
|
||||
message: message.to_string(),
|
||||
range: node.range(),
|
||||
});
|
||||
}
|
||||
|
||||
pub(super) fn extend(&mut self, diagnostics: &TypeCheckDiagnostics) {
|
||||
self.diagnostics.extend(diagnostics);
|
||||
}
|
||||
|
||||
pub(super) fn finish(mut self) -> TypeCheckDiagnostics {
|
||||
self.diagnostics.shrink_to_fit();
|
||||
self.diagnostics
|
||||
fn rule(&self) -> &str {
|
||||
&self.rule
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ use ruff_db::display::FormatterJoinExtension;
|
||||
use ruff_python_ast::str::Quote;
|
||||
use ruff_python_literal::escape::AsciiEscape;
|
||||
|
||||
use crate::types::{
|
||||
ClassLiteralType, InstanceType, IntersectionType, KnownClass, SubclassOfType, Type, UnionType,
|
||||
};
|
||||
use crate::types::{ClassLiteralType, InstanceType, IntersectionType, KnownClass, Type, UnionType};
|
||||
use crate::Db;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
@@ -66,7 +64,7 @@ impl Display for DisplayRepresentation<'_> {
|
||||
Type::Any => f.write_str("Any"),
|
||||
Type::Never => f.write_str("Never"),
|
||||
Type::Unknown => f.write_str("Unknown"),
|
||||
Type::Instance(InstanceType { class })
|
||||
Type::Instance(InstanceType { class, .. })
|
||||
if class.is_known(self.db, KnownClass::NoneType) =>
|
||||
{
|
||||
f.write_str("None")
|
||||
@@ -79,11 +77,10 @@ impl Display for DisplayRepresentation<'_> {
|
||||
}
|
||||
// TODO functions and classes should display using a fully qualified name
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => f.write_str(class.name(self.db)),
|
||||
Type::SubclassOf(SubclassOfType { class }) => {
|
||||
write!(f, "type[{}]", class.name(self.db))
|
||||
}
|
||||
Type::Instance(InstanceType { class }) => f.write_str(class.name(self.db)),
|
||||
Type::KnownInstance(known_instance) => f.write_str(known_instance.as_str()),
|
||||
Type::Instance(InstanceType { class, known }) => f.write_str(match known {
|
||||
Some(super::KnownInstance::Literal) => "Literal",
|
||||
_ => class.name(self.db),
|
||||
}),
|
||||
Type::FunctionLiteral(function) => f.write_str(function.name(self.db)),
|
||||
Type::Union(union) => union.display(self.db).fmt(f),
|
||||
Type::Intersection(intersection) => intersection.display(self.db).fmt(f),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ use indexmap::IndexSet;
|
||||
use itertools::Either;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, Type};
|
||||
use super::{Class, ClassLiteralType, KnownClass, Type};
|
||||
use crate::Db;
|
||||
|
||||
/// The inferred method resolution order of a given class.
|
||||
@@ -377,11 +377,7 @@ impl<'db> ClassBase<'db> {
|
||||
| Type::LiteralString
|
||||
| Type::Tuple(_)
|
||||
| Type::SliceLiteral(_)
|
||||
| Type::ModuleLiteral(_)
|
||||
| Type::SubclassOf(_) => None,
|
||||
Type::KnownInstance(known_instance) => match known_instance {
|
||||
KnownInstanceType::Literal => None,
|
||||
},
|
||||
| Type::ModuleLiteral(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::semantic_index::expression::Expression;
|
||||
use crate::semantic_index::symbol::{ScopeId, ScopedSymbolId, SymbolTable};
|
||||
use crate::semantic_index::symbol_table;
|
||||
use crate::types::{
|
||||
infer_expression_types, ClassLiteralType, InstanceType, IntersectionBuilder, KnownClass,
|
||||
KnownConstraintFunction, KnownFunction, Truthiness, Type, UnionBuilder,
|
||||
infer_expression_types, ClassLiteralType, IntersectionBuilder, KnownClass, KnownFunction,
|
||||
Truthiness, Type, UnionBuilder,
|
||||
};
|
||||
use crate::Db;
|
||||
use itertools::Itertools;
|
||||
@@ -78,27 +78,24 @@ fn all_negative_narrowing_constraints_for_expression<'db>(
|
||||
NarrowingConstraintsBuilder::new(db, ConstraintNode::Expression(expression), false).finish()
|
||||
}
|
||||
|
||||
/// Generate a constraint from the type of a `classinfo` argument to `isinstance` or `issubclass`.
|
||||
/// Generate a constraint from the *type* of the second argument of an `isinstance` call.
|
||||
///
|
||||
/// The `classinfo` argument can be a class literal, a tuple of (tuples of) class literals. PEP 604
|
||||
/// union types are not yet supported. Returns `None` if the `classinfo` argument has a wrong type.
|
||||
fn generate_classinfo_constraint<'db, F>(
|
||||
/// Example: for `isinstance(…, str)`, we would infer `Type::ClassLiteral(str)` from the
|
||||
/// second argument, but we need to generate a `Type::Instance(str)` constraint that can
|
||||
/// be used to narrow down the type of the first argument.
|
||||
fn generate_isinstance_constraint<'db>(
|
||||
db: &'db dyn Db,
|
||||
classinfo: &Type<'db>,
|
||||
to_constraint: F,
|
||||
) -> Option<Type<'db>>
|
||||
where
|
||||
F: Fn(ClassLiteralType<'db>) -> Type<'db> + Copy,
|
||||
{
|
||||
) -> Option<Type<'db>> {
|
||||
match classinfo {
|
||||
Type::ClassLiteral(ClassLiteralType { class }) => Some(Type::anonymous_instance(*class)),
|
||||
Type::Tuple(tuple) => {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
for element in tuple.elements(db) {
|
||||
builder = builder.add(generate_classinfo_constraint(db, element, to_constraint)?);
|
||||
builder = builder.add(generate_isinstance_constraint(db, element)?);
|
||||
}
|
||||
Some(builder.build())
|
||||
}
|
||||
Type::ClassLiteral(class_literal_type) => Some(to_constraint(*class_literal_type)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -333,51 +330,34 @@ impl<'db> NarrowingConstraintsBuilder<'db> {
|
||||
let scope = self.scope();
|
||||
let inference = infer_expression_types(self.db, expression);
|
||||
|
||||
// TODO: add support for PEP 604 union types on the right hand side of `isinstance`
|
||||
// and `issubclass`, for example `isinstance(x, str | (int | float))`.
|
||||
match inference
|
||||
if let Some(func_type) = inference
|
||||
.expression_ty(expr_call.func.scoped_ast_id(self.db, scope))
|
||||
.into_function_literal()
|
||||
.and_then(|f| f.known(self.db))
|
||||
.and_then(KnownFunction::constraint_function)
|
||||
{
|
||||
Some(function) if expr_call.arguments.keywords.is_empty() => {
|
||||
if let [ast::Expr::Name(ast::ExprName { id, .. }), class_info] =
|
||||
&*expr_call.arguments.args
|
||||
if func_type.is_known(self.db, KnownFunction::IsInstance)
|
||||
&& expr_call.arguments.keywords.is_empty()
|
||||
{
|
||||
if let [ast::Expr::Name(ast::ExprName { id, .. }), rhs] = &*expr_call.arguments.args
|
||||
{
|
||||
let symbol = self.symbols().symbol_id_by_name(id).unwrap();
|
||||
|
||||
let class_info_ty =
|
||||
inference.expression_ty(class_info.scoped_ast_id(self.db, scope));
|
||||
let rhs_type = inference.expression_ty(rhs.scoped_ast_id(self.db, scope));
|
||||
|
||||
let to_constraint = match function {
|
||||
KnownConstraintFunction::IsInstance => {
|
||||
|class_literal: ClassLiteralType<'db>| {
|
||||
Type::Instance(InstanceType {
|
||||
class: class_literal.class,
|
||||
})
|
||||
}
|
||||
// TODO: add support for PEP 604 union types on the right hand side:
|
||||
// isinstance(x, str | (int | float))
|
||||
if let Some(mut constraint) = generate_isinstance_constraint(self.db, &rhs_type)
|
||||
{
|
||||
if !is_positive {
|
||||
constraint = constraint.negate(self.db);
|
||||
}
|
||||
KnownConstraintFunction::IsSubclass => {
|
||||
|class_literal: ClassLiteralType<'db>| {
|
||||
Type::SubclassOf(class_literal.to_subclass_of_type())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
generate_classinfo_constraint(self.db, &class_info_ty, to_constraint).map(
|
||||
|constraint| {
|
||||
let mut constraints = NarrowingConstraints::default();
|
||||
constraints.insert(symbol, constraint.negate_if(self.db, !is_positive));
|
||||
constraints
|
||||
},
|
||||
)
|
||||
} else {
|
||||
None
|
||||
let mut constraints = NarrowingConstraints::default();
|
||||
constraints.insert(symbol, constraint);
|
||||
return Some(constraints);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn evaluate_match_pattern_singleton(
|
||||
|
||||
@@ -6,14 +6,14 @@ use rustc_hash::FxHashMap;
|
||||
|
||||
use crate::semantic_index::ast_ids::{HasScopedAstId, ScopedExpressionId};
|
||||
use crate::semantic_index::symbol::ScopeId;
|
||||
use crate::types::{TupleType, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder};
|
||||
use crate::types::{TupleType, Type};
|
||||
use crate::Db;
|
||||
|
||||
/// Unpacks the value expression type to their respective targets.
|
||||
pub(crate) struct Unpacker<'db> {
|
||||
db: &'db dyn Db,
|
||||
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
|
||||
diagnostics: TypeCheckDiagnosticsBuilder<'db>,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl<'db> Unpacker<'db> {
|
||||
@@ -21,7 +21,7 @@ impl<'db> Unpacker<'db> {
|
||||
Self {
|
||||
db,
|
||||
targets: FxHashMap::default(),
|
||||
diagnostics: TypeCheckDiagnosticsBuilder::new(db, file),
|
||||
file,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,9 +104,11 @@ impl<'db> Unpacker<'db> {
|
||||
let value_ty = if value_ty.is_literal_string() {
|
||||
Type::LiteralString
|
||||
} else {
|
||||
value_ty
|
||||
.iterate(self.db)
|
||||
.unwrap_with_diagnostic(AnyNodeRef::from(target), &mut self.diagnostics)
|
||||
value_ty.iterate(self.db).unwrap_with_diagnostic(
|
||||
self.db,
|
||||
AnyNodeRef::from(target),
|
||||
self.file,
|
||||
)
|
||||
};
|
||||
for element in elts {
|
||||
self.unpack(element, value_ty, scope);
|
||||
@@ -120,7 +122,6 @@ impl<'db> Unpacker<'db> {
|
||||
pub(crate) fn finish(mut self) -> UnpackResult<'db> {
|
||||
self.targets.shrink_to_fit();
|
||||
UnpackResult {
|
||||
diagnostics: self.diagnostics.finish(),
|
||||
targets: self.targets,
|
||||
}
|
||||
}
|
||||
@@ -129,15 +130,10 @@ impl<'db> Unpacker<'db> {
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub(crate) struct UnpackResult<'db> {
|
||||
targets: FxHashMap<ScopedExpressionId, Type<'db>>,
|
||||
diagnostics: TypeCheckDiagnostics,
|
||||
}
|
||||
|
||||
impl<'db> UnpackResult<'db> {
|
||||
pub(crate) fn get(&self, expr_id: ScopedExpressionId) -> Option<Type<'db>> {
|
||||
self.targets.get(&expr_id).copied()
|
||||
}
|
||||
|
||||
pub(crate) fn diagnostics(&self) -> &TypeCheckDiagnostics {
|
||||
&self.diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ use lsp_types::{
|
||||
RelatedFullDocumentDiagnosticReport, Url,
|
||||
};
|
||||
|
||||
use crate::edit::ToRangeExt;
|
||||
use red_knot_workspace::db::{Db, RootDatabase};
|
||||
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic as _, Severity};
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
|
||||
use crate::edit::ToRangeExt as _;
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::{client::Notifier, Result};
|
||||
use crate::session::DocumentSnapshot;
|
||||
use red_knot_workspace::db::{Db, RootDatabase};
|
||||
use ruff_db::diagnostic::Severity;
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
|
||||
pub(crate) struct DocumentDiagnosticRequestHandler;
|
||||
|
||||
@@ -72,7 +73,7 @@ fn compute_diagnostics(snapshot: &DocumentSnapshot, db: &RootDatabase) -> Vec<Di
|
||||
|
||||
fn to_lsp_diagnostic(
|
||||
db: &dyn Db,
|
||||
diagnostic: &dyn ruff_db::diagnostic::Diagnostic,
|
||||
diagnostic: &CompileDiagnostic,
|
||||
encoding: crate::PositionEncoding,
|
||||
) -> Diagnostic {
|
||||
let range = if let Some(range) = diagnostic.range() {
|
||||
|
||||
@@ -27,9 +27,8 @@ where
|
||||
.map(|diagnostic| DiagnosticWithLine {
|
||||
line_number: diagnostic
|
||||
.range()
|
||||
.map_or(OneIndexed::from_zero_indexed(0), |range| {
|
||||
line_index.line_index(range.start())
|
||||
}),
|
||||
.map(|range| line_index.line_index(range.start()))
|
||||
.unwrap_or(OneIndexed::from_zero_indexed(0)),
|
||||
diagnostic,
|
||||
})
|
||||
.collect();
|
||||
@@ -143,14 +142,13 @@ struct DiagnosticWithLine<T> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::db::Db;
|
||||
use crate::diagnostic::Diagnostic;
|
||||
use ruff_db::diagnostic::Severity;
|
||||
use ruff_db::diagnostic::{Diagnostic, Severity};
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::source::line_index;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
use ruff_source_file::OneIndexed;
|
||||
use ruff_text_size::{TextRange, TextSize};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[test]
|
||||
fn sort_and_group() {
|
||||
@@ -167,7 +165,7 @@ mod tests {
|
||||
|
||||
let diagnostics: Vec<_> = ranges
|
||||
.into_iter()
|
||||
.map(|range| DummyDiagnostic { range, file })
|
||||
.map(|range| DummyDiagnostic { file, range })
|
||||
.collect();
|
||||
|
||||
let sorted = super::SortedDiagnostics::new(diagnostics, &lines);
|
||||
@@ -185,8 +183,8 @@ mod tests {
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DummyDiagnostic {
|
||||
range: TextRange,
|
||||
file: File,
|
||||
range: TextRange,
|
||||
}
|
||||
|
||||
impl Diagnostic for DummyDiagnostic {
|
||||
@@ -194,8 +192,8 @@ mod tests {
|
||||
"dummy"
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
"dummy".into()
|
||||
fn message(&self) -> std::borrow::Cow<str> {
|
||||
"Dummy error".into()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use colored::Colorize;
|
||||
use parser as test_parser;
|
||||
use red_knot_python_semantic::types::check_types;
|
||||
use ruff_db::diagnostic::{Diagnostic, ParseDiagnostic};
|
||||
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic as _};
|
||||
use ruff_db::files::{system_path_to_file, File, Files};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
|
||||
use ruff_source_file::LineIndex;
|
||||
use ruff_text_size::TextSize;
|
||||
@@ -86,24 +85,13 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
|
||||
let failures: Failures = test_files
|
||||
.into_iter()
|
||||
.filter_map(|test_file| {
|
||||
let parsed = parsed_module(db, test_file.file);
|
||||
|
||||
let mut diagnostics: Vec<Box<_>> = parsed
|
||||
.errors()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|error| {
|
||||
let diagnostic: Box<dyn Diagnostic> =
|
||||
Box::new(ParseDiagnostic::new(test_file.file, error));
|
||||
diagnostic
|
||||
})
|
||||
.collect();
|
||||
|
||||
let type_diagnostics = check_types(db, test_file.file);
|
||||
diagnostics.extend(type_diagnostics.into_iter().map(|diagnostic| {
|
||||
let diagnostic: Box<dyn Diagnostic> = Box::new((*diagnostic).clone());
|
||||
diagnostic
|
||||
}));
|
||||
let diagnostics: Vec<_> =
|
||||
// The accumulator returns all diagnostics from all files. We're only interested in
|
||||
// diagnostics from this file.
|
||||
check_types::accumulated::<CompileDiagnostic>(db, test_file.file)
|
||||
.into_iter()
|
||||
.filter(|diagnostic| diagnostic.file() == test_file.file)
|
||||
.collect();
|
||||
|
||||
match matcher::match_file(db, test_file.file, diagnostics) {
|
||||
Ok(()) => None,
|
||||
|
||||
@@ -318,6 +318,7 @@ mod tests {
|
||||
use ruff_text_size::TextRange;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ExpectedDiagnostic {
|
||||
rule: &'static str,
|
||||
message: &'static str,
|
||||
@@ -336,20 +337,20 @@ mod tests {
|
||||
|
||||
fn into_diagnostic(self, file: File) -> TestDiagnostic {
|
||||
TestDiagnostic {
|
||||
file,
|
||||
rule: self.rule,
|
||||
message: self.message,
|
||||
range: self.range,
|
||||
file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestDiagnostic {
|
||||
file: File,
|
||||
rule: &'static str,
|
||||
message: &'static str,
|
||||
range: TextRange,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl Diagnostic for TestDiagnostic {
|
||||
@@ -358,7 +359,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
self.message.into()
|
||||
Cow::Borrowed(self.message)
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
@@ -384,15 +385,15 @@ mod tests {
|
||||
db.write_file("/src/test.py", source).unwrap();
|
||||
let file = system_path_to_file(&db, "/src/test.py").unwrap();
|
||||
|
||||
super::match_file(
|
||||
&db,
|
||||
file,
|
||||
diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.into_diagnostic(file)),
|
||||
)
|
||||
let diagnostics: Vec<_> = diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.into_diagnostic(file))
|
||||
.collect();
|
||||
|
||||
super::match_file(&db, file, diagnostics)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_fail(result: Result<(), FailuresByLine>, messages: &[(usize, &[&str])]) {
|
||||
let Err(failures) = result else {
|
||||
panic!("expected a failure");
|
||||
@@ -415,6 +416,7 @@ mod tests {
|
||||
assert_eq!(failures, expected);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_ok(result: &Result<(), FailuresByLine>) {
|
||||
assert!(result.is_ok(), "{result:?}");
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::any::Any;
|
||||
|
||||
use js_sys::Error;
|
||||
use ruff_db::diagnostic::CompileDiagnostic;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use red_knot_workspace::db::RootDatabase;
|
||||
use red_knot_workspace::db::{Db, RootDatabase};
|
||||
use red_knot_workspace::workspace::settings::Configuration;
|
||||
use red_knot_workspace::workspace::WorkspaceMetadata;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::system::walk_directory::WalkDirectoryBuilder;
|
||||
use ruff_db::system::{
|
||||
@@ -111,20 +111,14 @@ impl Workspace {
|
||||
pub fn check_file(&self, file_id: &FileHandle) -> Result<Vec<String>, Error> {
|
||||
let result = self.db.check_file(file_id.file).map_err(into_error)?;
|
||||
|
||||
Ok(result
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.display(&self.db).to_string())
|
||||
.collect())
|
||||
Ok(map_diagnostics(&self.db, result))
|
||||
}
|
||||
|
||||
/// Checks all open files
|
||||
pub fn check(&self) -> Result<Vec<String>, Error> {
|
||||
let result = self.db.check().map_err(into_error)?;
|
||||
|
||||
Ok(result
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.display(&self.db).to_string())
|
||||
.collect())
|
||||
Ok(map_diagnostics(&self.db, result))
|
||||
}
|
||||
|
||||
/// Returns the parsed AST for `path`
|
||||
@@ -149,6 +143,13 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
fn map_diagnostics(db: &dyn Db, diagnostics: Vec<CompileDiagnostic>) -> Vec<String> {
|
||||
diagnostics
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.display(db.upcast()).to_string())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn into_error<E: std::fmt::Display>(err: E) -> Error {
|
||||
Error::new(&err.to_string())
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ red_knot_python_semantic = { workspace = true }
|
||||
ruff_cache = { workspace = true }
|
||||
ruff_db = { workspace = true, features = ["os", "cache"] }
|
||||
ruff_python_ast = { workspace = true }
|
||||
ruff_text_size = { workspace = true }
|
||||
red_knot_vendored = { workspace = true }
|
||||
|
||||
anyhow = { workspace = true }
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
use std::panic::RefUnwindSafe;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ruff_db::diagnostic::CompileDiagnostic;
|
||||
use salsa::plumbing::ZalsaDatabase;
|
||||
use salsa::{Cancelled, Event};
|
||||
|
||||
use crate::workspace::{check_file, Workspace, WorkspaceMetadata};
|
||||
use red_knot_python_semantic::{Db as SemanticDb, Program};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{File, Files};
|
||||
use ruff_db::system::System;
|
||||
use ruff_db::vendored::VendoredFileSystem;
|
||||
use ruff_db::{Db as SourceDb, Upcast};
|
||||
|
||||
use crate::workspace::{Workspace, WorkspaceMetadata};
|
||||
|
||||
mod changes;
|
||||
|
||||
#[salsa::db]
|
||||
@@ -51,14 +52,14 @@ impl RootDatabase {
|
||||
}
|
||||
|
||||
/// Checks all open files in the workspace and its dependencies.
|
||||
pub fn check(&self) -> Result<Vec<Box<dyn Diagnostic>>, Cancelled> {
|
||||
pub fn check(&self) -> Result<Vec<CompileDiagnostic>, Cancelled> {
|
||||
self.with_db(|db| db.workspace().check(db))
|
||||
}
|
||||
|
||||
pub fn check_file(&self, file: File) -> Result<Vec<Box<dyn Diagnostic>>, Cancelled> {
|
||||
pub fn check_file(&self, file: File) -> Result<Vec<CompileDiagnostic>, Cancelled> {
|
||||
let _span = tracing::debug_span!("check_file", file=%file.path(self)).entered();
|
||||
|
||||
self.with_db(|db| check_file(db, file))
|
||||
self.with_db(|db| db.workspace().check_file(db, file))
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the system.
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use salsa::{Durability, Setter as _};
|
||||
use std::borrow::Cow;
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::db::RootDatabase;
|
||||
use crate::workspace::files::{Index, Indexed, IndexedIter, PackageFiles};
|
||||
use ruff_db::diagnostic::{CompileDiagnostic, Diagnostic};
|
||||
use rustc_hash::{FxBuildHasher, FxHashSet};
|
||||
use salsa::{Durability, Setter as _};
|
||||
|
||||
pub use metadata::{PackageMetadata, WorkspaceMetadata};
|
||||
use red_knot_python_semantic::types::check_types;
|
||||
use red_knot_python_semantic::SearchPathSettings;
|
||||
use ruff_db::diagnostic::{Diagnostic, ParseDiagnostic, Severity};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_db::source::{source_text, SourceTextError};
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_db::{
|
||||
files::{system_path_to_file, File},
|
||||
system::{walk_directory::WalkState, SystemPath, SystemPathBuf},
|
||||
};
|
||||
use ruff_python_ast::{name::Name, PySourceType};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::db::Db;
|
||||
use crate::db::RootDatabase;
|
||||
use crate::workspace::files::{Index, Indexed, IndexedIter, PackageFiles};
|
||||
|
||||
mod files;
|
||||
mod metadata;
|
||||
@@ -188,7 +187,7 @@ impl Workspace {
|
||||
}
|
||||
|
||||
/// Checks all open files in the workspace and its dependencies.
|
||||
pub fn check(self, db: &RootDatabase) -> Vec<Box<dyn Diagnostic>> {
|
||||
pub fn check(self, db: &RootDatabase) -> Vec<CompileDiagnostic> {
|
||||
let workspace_span = tracing::debug_span!("check_workspace");
|
||||
let _span = workspace_span.enter();
|
||||
|
||||
@@ -200,6 +199,13 @@ impl Workspace {
|
||||
let db = db.snapshot();
|
||||
let workspace_span = workspace_span.clone();
|
||||
|
||||
// TODO: Checking per-file and then filtering out the diagnostics from other files
|
||||
// isn't the ideal solution but doing it "properly" requires support for
|
||||
// ["parallel Salsa"](https://github.com/salsa-rs/salsa/pull/568).
|
||||
//
|
||||
// The solution with parallel Salsa is:
|
||||
// * Create a new `check_workspace_impl` query similar to `check_file_impl`
|
||||
// * Collect the workspace diagnostics using `check_workspace_impl::accumulated::<CompileDiagnostic>(&db, file)`
|
||||
rayon::scope(move |scope| {
|
||||
for file in &files {
|
||||
let result = inner_result.clone();
|
||||
@@ -210,8 +216,17 @@ impl Workspace {
|
||||
let check_file_span = tracing::debug_span!(parent: &workspace_span, "check_file", file=%file.path(&db));
|
||||
let _entered = check_file_span.entered();
|
||||
|
||||
let file_diagnostics = check_file(&db, file);
|
||||
result.lock().unwrap().extend(file_diagnostics);
|
||||
// Filter out the diagnostics from other files to avoid duplicates.
|
||||
// This should no longer be necessary with parallel-salsa where
|
||||
// it's possible to call the query for the entire workspace.
|
||||
let mut file_diagnostics =
|
||||
check_file_impl::accumulated::<CompileDiagnostic>(&db, file);
|
||||
|
||||
file_diagnostics.sort_unstable_by_key(|a| {
|
||||
a.range().unwrap_or_default().start()
|
||||
});
|
||||
|
||||
result.lock().unwrap().extend(file_diagnostics.into_iter().filter(|diagnostic| diagnostic.file() == file));
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -219,6 +234,20 @@ impl Workspace {
|
||||
Arc::into_inner(result).unwrap().into_inner().unwrap()
|
||||
}
|
||||
|
||||
pub fn check_file(self, db: &dyn Db, file: File) -> Vec<CompileDiagnostic> {
|
||||
let diagnostics = check_file_impl::accumulated::<CompileDiagnostic>(db, file);
|
||||
|
||||
let mut file_diagnostics: Vec<_> = diagnostics
|
||||
.into_iter()
|
||||
.filter(|diagnostic| diagnostic.file() == file)
|
||||
.collect();
|
||||
|
||||
file_diagnostics
|
||||
.sort_unstable_by_key(|diagnostic| diagnostic.range().unwrap_or_default().start());
|
||||
|
||||
file_diagnostics
|
||||
}
|
||||
|
||||
/// Opens a file in the workspace.
|
||||
///
|
||||
/// This changes the behavior of `check` to only check the open files rather than all files in the workspace.
|
||||
@@ -378,33 +407,28 @@ impl Package {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn check_file(db: &dyn Db, file: File) -> Vec<Box<dyn Diagnostic>> {
|
||||
let mut diagnostics: Vec<Box<dyn Diagnostic>> = Vec::new();
|
||||
// Abort checking if there are IO errors.
|
||||
let source = source_text(db.upcast(), file);
|
||||
/// Checks a single file
|
||||
///
|
||||
/// This is a Salsa query so that [`Workspace::check_file`] can retrieve the accumulated diagnostics.
|
||||
#[salsa::tracked]
|
||||
pub(super) fn check_file_impl(db: &dyn Db, file: File) {
|
||||
tracing::debug!("Checking file '{path}'", path = file.path(db));
|
||||
|
||||
if let Some(read_error) = source.read_error() {
|
||||
diagnostics.push(Box::new(IOErrorDiagnostic {
|
||||
file,
|
||||
error: read_error.clone(),
|
||||
}));
|
||||
return diagnostics;
|
||||
// Abort checking if there are IO errors.
|
||||
if source_text(db.upcast(), file).has_read_error() {
|
||||
return;
|
||||
}
|
||||
|
||||
let parsed = parsed_module(db.upcast(), file);
|
||||
diagnostics.extend(parsed.errors().iter().map(|error| {
|
||||
let diagnostic: Box<dyn Diagnostic> = Box::new(ParseDiagnostic::new(file, error.clone()));
|
||||
diagnostic
|
||||
}));
|
||||
check_types(db.upcast(), file);
|
||||
}
|
||||
|
||||
diagnostics.extend(check_types(db.upcast(), file).iter().map(|diagnostic| {
|
||||
let boxed: Box<dyn Diagnostic> = Box::new(diagnostic.clone());
|
||||
boxed
|
||||
}));
|
||||
#[salsa::tracked]
|
||||
fn check_workspace_sync(db: &dyn Db, workspace: Workspace) {
|
||||
let files = WorkspaceFiles::new(db, workspace);
|
||||
|
||||
diagnostics.sort_unstable_by_key(|diagnostic| diagnostic.range().unwrap_or_default().start());
|
||||
|
||||
diagnostics
|
||||
for file in &files {
|
||||
check_file_impl(db, file);
|
||||
}
|
||||
}
|
||||
|
||||
fn discover_package_files(db: &dyn Db, path: &SystemPath) -> FxHashSet<File> {
|
||||
@@ -517,48 +541,46 @@ impl Iterator for WorkspaceFilesIter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IOErrorDiagnostic {
|
||||
file: File,
|
||||
error: SourceTextError,
|
||||
}
|
||||
|
||||
impl Diagnostic for IOErrorDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
"io"
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
self.error.to_string().into()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
None
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Error
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::db::tests::TestDb;
|
||||
use crate::workspace::check_file;
|
||||
use red_knot_python_semantic::types::check_types;
|
||||
use red_knot_python_semantic::{
|
||||
ProgramSettings, PythonVersion, SearchPathSettings, SitePackages,
|
||||
};
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::system_path_to_file;
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPath};
|
||||
use ruff_db::system::{DbWithTestSystem, SystemPath, SystemPathBuf};
|
||||
use ruff_db::testing::assert_function_query_was_not_run;
|
||||
|
||||
use crate::db::tests::TestDb;
|
||||
|
||||
use crate::workspace::settings::WorkspaceSettings;
|
||||
use crate::workspace::{Workspace, WorkspaceMetadata};
|
||||
|
||||
#[test]
|
||||
fn check_file_skips_type_checking_when_file_cant_be_read() -> ruff_db::system::Result<()> {
|
||||
let mut db = TestDb::new();
|
||||
let root = SystemPathBuf::from("src");
|
||||
let workspace = Workspace::from_metadata(
|
||||
&db,
|
||||
WorkspaceMetadata {
|
||||
root: root.clone(),
|
||||
packages: vec![],
|
||||
settings: WorkspaceSettings {
|
||||
program: ProgramSettings {
|
||||
target_version: PythonVersion::default(),
|
||||
search_paths: SearchPathSettings {
|
||||
extra_paths: vec![],
|
||||
src_root: root,
|
||||
custom_typeshed: None,
|
||||
site_packages: SitePackages::Known(vec![]),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let path = SystemPath::new("test.py");
|
||||
|
||||
db.write_file(path, "x = 10")?;
|
||||
@@ -568,12 +590,16 @@ mod tests {
|
||||
db.memory_file_system().remove_file(path)?;
|
||||
file.sync(&mut db);
|
||||
|
||||
let diagnostics: Vec<_> = workspace
|
||||
.check_file(&db, file)
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.message().to_string())
|
||||
.collect();
|
||||
|
||||
assert_eq!(source_text(&db, file).as_str(), "");
|
||||
|
||||
assert_eq!(
|
||||
check_file(&db, file)
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.message().into_owned())
|
||||
.collect::<Vec<_>>(),
|
||||
diagnostics,
|
||||
vec!["Failed to read file: No such file or directory".to_string()]
|
||||
);
|
||||
|
||||
@@ -584,14 +610,14 @@ mod tests {
|
||||
// content returned by `source_text` remains unchanged, but the diagnostics should get updated.
|
||||
db.write_file(path, "").unwrap();
|
||||
|
||||
let diagnostics: Vec<_> = workspace
|
||||
.check_file(&db, file)
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.message().to_string())
|
||||
.collect();
|
||||
|
||||
assert_eq!(source_text(&db, file).as_str(), "");
|
||||
assert_eq!(
|
||||
check_file(&db, file)
|
||||
.into_iter()
|
||||
.map(|diagnostic| diagnostic.message().into_owned())
|
||||
.collect::<Vec<_>>(),
|
||||
vec![] as Vec<String>
|
||||
);
|
||||
assert_eq!(diagnostics, vec![] as Vec<String>);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
publish = true
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -8,7 +8,6 @@ use red_knot_workspace::workspace::settings::Configuration;
|
||||
use red_knot_workspace::workspace::WorkspaceMetadata;
|
||||
use ruff_benchmark::criterion::{criterion_group, criterion_main, BatchSize, Criterion};
|
||||
use ruff_benchmark::TestFile;
|
||||
use ruff_db::diagnostic::Diagnostic;
|
||||
use ruff_db::files::{system_path_to_file, File};
|
||||
use ruff_db::source::source_text;
|
||||
use ruff_db::system::{MemoryFileSystem, SystemPath, SystemPathBuf, TestSystem};
|
||||
@@ -124,7 +123,6 @@ fn setup_rayon() {
|
||||
fn benchmark_incremental(criterion: &mut Criterion) {
|
||||
fn setup() -> Case {
|
||||
let case = setup_case();
|
||||
|
||||
let result: Vec<_> = case
|
||||
.db
|
||||
.check()
|
||||
|
||||
@@ -28,7 +28,6 @@ matchit = { workspace = true }
|
||||
salsa = { workspace = true }
|
||||
serde = { workspace = true, optional = true }
|
||||
path-slash = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, optional = true }
|
||||
tracing-tree = { workspace = true, optional = true }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use ruff_text_size::TextRange;
|
||||
use salsa::Accumulator as _;
|
||||
|
||||
use crate::{
|
||||
files::File,
|
||||
source::{line_index, source_text},
|
||||
Db,
|
||||
};
|
||||
use ruff_python_parser::ParseError;
|
||||
use ruff_text_size::TextRange;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub trait Diagnostic: Send + Sync + std::fmt::Debug {
|
||||
fn rule(&self) -> &str;
|
||||
@@ -17,16 +17,6 @@ pub trait Diagnostic: Send + Sync + std::fmt::Debug {
|
||||
fn range(&self) -> Option<TextRange>;
|
||||
|
||||
fn severity(&self) -> Severity;
|
||||
|
||||
fn display<'a>(&'a self, db: &'a dyn Db) -> DisplayDiagnostic<'a>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
DisplayDiagnostic {
|
||||
db,
|
||||
diagnostic: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -35,6 +25,47 @@ pub enum Severity {
|
||||
Error,
|
||||
}
|
||||
|
||||
#[salsa::accumulator]
|
||||
pub struct CompileDiagnostic(std::sync::Arc<dyn Diagnostic>);
|
||||
|
||||
impl CompileDiagnostic {
|
||||
pub fn report<T>(db: &dyn Db, diagnostic: T)
|
||||
where
|
||||
T: Diagnostic + 'static,
|
||||
{
|
||||
Self(std::sync::Arc::new(diagnostic)).accumulate(db);
|
||||
}
|
||||
|
||||
pub fn display<'a>(&'a self, db: &'a dyn Db) -> DisplayDiagnostic<'a> {
|
||||
DisplayDiagnostic {
|
||||
db,
|
||||
diagnostic: &*self.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for CompileDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
self.0.rule()
|
||||
}
|
||||
|
||||
fn message(&self) -> std::borrow::Cow<str> {
|
||||
self.0.message()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
self.0.file()
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
self.0.range()
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
self.0.severity()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisplayDiagnostic<'db> {
|
||||
db: &'db dyn Db,
|
||||
diagnostic: &'db dyn Diagnostic,
|
||||
@@ -81,7 +112,7 @@ where
|
||||
(**self).rule()
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
fn message(&self) -> std::borrow::Cow<str> {
|
||||
(**self).message()
|
||||
}
|
||||
|
||||
@@ -122,59 +153,3 @@ where
|
||||
(**self).severity()
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for Box<dyn Diagnostic> {
|
||||
fn rule(&self) -> &str {
|
||||
(**self).rule()
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
(**self).message()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
(**self).file()
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
(**self).range()
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
(**self).severity()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseDiagnostic {
|
||||
file: File,
|
||||
error: ParseError,
|
||||
}
|
||||
|
||||
impl ParseDiagnostic {
|
||||
pub fn new(file: File, error: ParseError) -> Self {
|
||||
Self { file, error }
|
||||
}
|
||||
}
|
||||
|
||||
impl Diagnostic for ParseDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
"invalid-syntax"
|
||||
}
|
||||
|
||||
fn message(&self) -> Cow<str> {
|
||||
self.error.error.to_string().into()
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<TextRange> {
|
||||
Some(self.error.location)
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Formatter;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ruff_python_ast::{ModModule, PySourceType};
|
||||
use ruff_python_parser::{parse_unchecked_source, Parsed};
|
||||
use ruff_text_size::TextRange;
|
||||
|
||||
use crate::diagnostic::{CompileDiagnostic, Diagnostic};
|
||||
use crate::files::{File, FilePath};
|
||||
use crate::source::source_text;
|
||||
use crate::Db;
|
||||
@@ -37,7 +40,20 @@ pub fn parsed_module(db: &dyn Db, file: File) -> ParsedModule {
|
||||
.map_or(PySourceType::Python, PySourceType::from_extension),
|
||||
};
|
||||
|
||||
ParsedModule::new(parse_unchecked_source(&source, ty))
|
||||
let parsed = parse_unchecked_source(&source, ty);
|
||||
|
||||
for error in parsed.errors() {
|
||||
CompileDiagnostic::report(
|
||||
db,
|
||||
ParseDiagnostic {
|
||||
error: error.error.to_string(),
|
||||
range: error.location,
|
||||
file,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ParsedModule::new(parsed)
|
||||
}
|
||||
|
||||
/// Cheap cloneable wrapper around the parsed module.
|
||||
@@ -73,6 +89,35 @@ impl std::fmt::Debug for ParsedModule {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseDiagnostic {
|
||||
error: String,
|
||||
range: TextRange,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl Diagnostic for ParseDiagnostic {
|
||||
fn rule(&self) -> &str {
|
||||
"invalid-syntax"
|
||||
}
|
||||
|
||||
fn message(&self) -> std::borrow::Cow<str> {
|
||||
Cow::Borrowed(&self.error)
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<ruff_text_size::TextRange> {
|
||||
Some(self.range)
|
||||
}
|
||||
|
||||
fn severity(&self) -> crate::diagnostic::Severity {
|
||||
crate::diagnostic::Severity::Error
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::files::{system_path_to_file, vendored_path_to_file};
|
||||
|
||||
@@ -7,6 +7,7 @@ use ruff_notebook::Notebook;
|
||||
use ruff_python_ast::PySourceType;
|
||||
use ruff_source_file::LineIndex;
|
||||
|
||||
use crate::diagnostic::{CompileDiagnostic, Diagnostic};
|
||||
use crate::files::{File, FilePath};
|
||||
use crate::Db;
|
||||
|
||||
@@ -15,14 +16,22 @@ use crate::Db;
|
||||
pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
let path = file.path(db);
|
||||
let _span = tracing::trace_span!("source_text", file = %path).entered();
|
||||
let mut read_error = None;
|
||||
let mut has_read_error = false;
|
||||
|
||||
let kind = if is_notebook(file.path(db)) {
|
||||
file.read_to_notebook(db)
|
||||
.unwrap_or_else(|error| {
|
||||
tracing::debug!("Failed to read notebook '{path}': {error}");
|
||||
|
||||
read_error = Some(SourceTextError::FailedToReadNotebook(error.to_string()));
|
||||
has_read_error = true;
|
||||
CompileDiagnostic::report(
|
||||
db,
|
||||
SourceTextDiagnostic {
|
||||
error: SourceTextError::FailedToReadNotebook(error),
|
||||
file,
|
||||
},
|
||||
);
|
||||
|
||||
Notebook::empty()
|
||||
})
|
||||
.into()
|
||||
@@ -31,7 +40,15 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
.unwrap_or_else(|error| {
|
||||
tracing::debug!("Failed to read file '{path}': {error}");
|
||||
|
||||
read_error = Some(SourceTextError::FailedToReadFile(error.to_string()));
|
||||
has_read_error = true;
|
||||
CompileDiagnostic::report(
|
||||
db,
|
||||
SourceTextDiagnostic {
|
||||
error: SourceTextError::FailedToReadFile(error),
|
||||
file,
|
||||
},
|
||||
);
|
||||
|
||||
String::new()
|
||||
})
|
||||
.into()
|
||||
@@ -40,7 +57,7 @@ pub fn source_text(db: &dyn Db, file: File) -> SourceText {
|
||||
SourceText {
|
||||
inner: Arc::new(SourceTextInner {
|
||||
kind,
|
||||
read_error,
|
||||
has_read_error,
|
||||
count: Count::new(),
|
||||
}),
|
||||
}
|
||||
@@ -93,8 +110,8 @@ impl SourceText {
|
||||
}
|
||||
|
||||
/// Returns `true` if there was an error when reading the content of the file.
|
||||
pub fn read_error(&self) -> Option<&SourceTextError> {
|
||||
self.inner.read_error.as_ref()
|
||||
pub fn has_read_error(&self) -> bool {
|
||||
self.inner.has_read_error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +144,7 @@ impl std::fmt::Debug for SourceText {
|
||||
struct SourceTextInner {
|
||||
count: Count<SourceText>,
|
||||
kind: SourceTextKind,
|
||||
read_error: Option<SourceTextError>,
|
||||
has_read_error: bool,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
@@ -148,12 +165,45 @@ impl From<Notebook> for SourceTextKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct SourceTextDiagnostic {
|
||||
error: SourceTextError,
|
||||
file: File,
|
||||
}
|
||||
|
||||
impl Diagnostic for SourceTextDiagnostic {
|
||||
fn message(&self) -> std::borrow::Cow<str> {
|
||||
match &self.error {
|
||||
SourceTextError::FailedToReadNotebook(notebook_error) => {
|
||||
format!("Failed to read notebook: {notebook_error}").into()
|
||||
}
|
||||
SourceTextError::FailedToReadFile(error) => {
|
||||
format!("Failed to read file: {error}").into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn rule(&self) -> &str {
|
||||
"io-error"
|
||||
}
|
||||
|
||||
fn file(&self) -> File {
|
||||
self.file
|
||||
}
|
||||
|
||||
fn severity(&self) -> crate::diagnostic::Severity {
|
||||
crate::diagnostic::Severity::Error
|
||||
}
|
||||
|
||||
fn range(&self) -> Option<ruff_text_size::TextRange> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SourceTextError {
|
||||
#[error("Failed to read notebook: {0}`")]
|
||||
FailedToReadNotebook(String),
|
||||
#[error("Failed to read file: {0}")]
|
||||
FailedToReadFile(String),
|
||||
FailedToReadNotebook(ruff_notebook::NotebookError),
|
||||
FailedToReadFile(std::io::Error),
|
||||
}
|
||||
|
||||
/// Computes the [`LineIndex`] for `file`.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ruff_linter"
|
||||
version = "0.7.3"
|
||||
version = "0.7.2"
|
||||
publish = false
|
||||
authors = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
|
||||
@@ -10,8 +10,6 @@ async def func3(id, dir):
|
||||
pass
|
||||
|
||||
|
||||
# this is Ok for A002 (trigger A005 instead)
|
||||
# https://github.com/astral-sh/ruff/issues/14135
|
||||
map([], lambda float: ...)
|
||||
|
||||
from typing import override, overload
|
||||
|
||||
@@ -3,8 +3,3 @@ lambda x, float, y: x + y
|
||||
lambda min, max: min
|
||||
lambda id: id
|
||||
lambda dir: dir
|
||||
|
||||
# Ok for A006 - should trigger A002 instead
|
||||
# https://github.com/astral-sh/ruff/issues/14135
|
||||
def func1(str, /, type, *complex, Exception, **getattr):
|
||||
pass
|
||||
@@ -6,19 +6,6 @@ def foo():
|
||||
class Bar:
|
||||
"""bar""" # ERROR PYI021
|
||||
|
||||
class Qux:
|
||||
"""qux""" # ERROR PYI021
|
||||
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
class Baz:
|
||||
"""Multiline docstring
|
||||
|
||||
Lorem ipsum dolor sit amet
|
||||
"""
|
||||
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
def bar():
|
||||
x = 1
|
||||
"""foo""" # OK, not a doc string
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# setup
|
||||
from enum import Enum, EnumMeta
|
||||
from collections import UserList as UL
|
||||
|
||||
class SetOnceMappingMixin:
|
||||
__slots__ = ()
|
||||
def __setitem__(self, key, value):
|
||||
if key in self:
|
||||
raise KeyError(str(key) + ' already set')
|
||||
return super().__setitem__(key, value)
|
||||
|
||||
|
||||
class CaseInsensitiveEnumMeta(EnumMeta):
|
||||
pass
|
||||
|
||||
# positives
|
||||
class D(dict):
|
||||
pass
|
||||
|
||||
class L(list):
|
||||
pass
|
||||
|
||||
class S(str):
|
||||
pass
|
||||
|
||||
# currently not detected
|
||||
class SetOnceDict(SetOnceMappingMixin, dict):
|
||||
pass
|
||||
|
||||
# negatives
|
||||
class C:
|
||||
pass
|
||||
|
||||
class I(int):
|
||||
pass
|
||||
|
||||
class ActivityState(str, Enum, metaclass=CaseInsensitiveEnumMeta):
|
||||
"""Activity state. This is an optional property and if not provided, the state will be Active by
|
||||
default.
|
||||
"""
|
||||
ACTIVE = "Active"
|
||||
INACTIVE = "Inactive"
|
||||
@@ -155,7 +155,7 @@ pub(crate) fn definitions(checker: &mut Checker) {
|
||||
|
||||
// flake8-pyi
|
||||
if enforce_stubs {
|
||||
flake8_pyi::rules::docstring_in_stubs(checker, definition, docstring);
|
||||
flake8_pyi::rules::docstring_in_stubs(checker, docstring);
|
||||
}
|
||||
if enforce_stubs_and_runtime {
|
||||
flake8_pyi::rules::iter_method_return_iterable(checker, definition);
|
||||
|
||||
@@ -549,9 +549,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
|
||||
if checker.enabled(Rule::WhitespaceAfterDecorator) {
|
||||
pycodestyle::rules::whitespace_after_decorator(checker, decorator_list);
|
||||
}
|
||||
if checker.enabled(Rule::SubclassBuiltin) {
|
||||
refurb::rules::subclass_builtin(checker, class_def);
|
||||
}
|
||||
}
|
||||
Stmt::Import(ast::StmtImport { names, range: _ }) => {
|
||||
if checker.enabled(Rule::MultipleImportsOnOneLine) {
|
||||
|
||||
@@ -1072,7 +1072,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Refurb, "181") => (RuleGroup::Stable, rules::refurb::rules::HashlibDigestHex),
|
||||
(Refurb, "187") => (RuleGroup::Stable, rules::refurb::rules::ListReverseCopy),
|
||||
(Refurb, "188") => (RuleGroup::Preview, rules::refurb::rules::SliceToRemovePrefixOrSuffix),
|
||||
(Refurb, "189") => (RuleGroup::Preview, rules::refurb::rules::SubclassBuiltin),
|
||||
(Refurb, "192") => (RuleGroup::Preview, rules::refurb::rules::SortedMinMax),
|
||||
|
||||
// flake8-logging
|
||||
|
||||
@@ -16,43 +16,8 @@ static CODE_INDICATORS: LazyLock<AhoCorasick> = LazyLock::new(|| {
|
||||
|
||||
static ALLOWLIST_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(
|
||||
r"(?x)
|
||||
^
|
||||
(?:
|
||||
# Case-sensitive
|
||||
pyright
|
||||
| mypy:
|
||||
| type:\s*ignore
|
||||
| SPDX-License-Identifier:
|
||||
| fmt:\s*(on|off|skip)
|
||||
| region|endregion
|
||||
|
||||
# Case-insensitive
|
||||
| (?i:
|
||||
noqa
|
||||
)
|
||||
|
||||
# Unknown case sensitivity
|
||||
| (?i:
|
||||
pylint
|
||||
| nosec
|
||||
| isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)
|
||||
| (?:en)?coding[:=][\x20\t]*([-_.A-Z0-9]+)
|
||||
)
|
||||
|
||||
# IntelliJ language injection comments:
|
||||
# * `language` must be lowercase.
|
||||
# * No spaces around `=`.
|
||||
# * Language IDs as used in comments must have no spaces,
|
||||
# though to IntelliJ they can be anything.
|
||||
# * May optionally contain `prefix=` and/or `suffix=`,
|
||||
# not declared here since we use `.is_match()`.
|
||||
| language=[-_.a-zA-Z0-9]+
|
||||
|
||||
)
|
||||
",
|
||||
)
|
||||
.unwrap()
|
||||
r"^(?i)(?:pylint|pyright|noqa|nosec|region|endregion|type:\s*ignore|fmt:\s*(on|off)|isort:\s*(on|off|skip|skip_file|split|dont-add-imports(:\s*\[.*?])?)|mypy:|SPDX-License-Identifier:|language=[a-zA-Z](?: ?[-_.a-zA-Z0-9]+)+(?:\s+prefix=\S+)?(?:\s+suffix=\S+)?|(?:en)?coding[:=][ \t]*([-_.a-zA-Z0-9]+))",
|
||||
).unwrap()
|
||||
});
|
||||
|
||||
static HASH_NUMBER: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"#\d").unwrap());
|
||||
@@ -334,42 +299,17 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn comment_contains_language_injection() {
|
||||
// `language` with bad casing
|
||||
assert!(comment_contains_code("# Language=C#", &[]));
|
||||
assert!(comment_contains_code("# lAngUAgE=inI", &[]));
|
||||
|
||||
// Unreasonable language IDs, possibly literals
|
||||
assert!(comment_contains_code("# language=123", &[]));
|
||||
assert!(comment_contains_code("# language=\"pt\"", &[]));
|
||||
assert!(comment_contains_code("# language='en'", &[]));
|
||||
|
||||
// Spaces around equal sign
|
||||
assert!(comment_contains_code("# language =xml", &[]));
|
||||
assert!(comment_contains_code("# language= html", &[]));
|
||||
assert!(comment_contains_code("# language = RegExp", &[]));
|
||||
|
||||
// Leading whitespace
|
||||
assert!(!comment_contains_code("#language=CSS", &[]));
|
||||
assert!(!comment_contains_code("# \t language=C++", &[]));
|
||||
|
||||
// Human language false negatives
|
||||
assert!(!comment_contains_code("# language=en", &[]));
|
||||
assert!(!comment_contains_code("# language=en-US", &[]));
|
||||
|
||||
// Casing (fine because such IDs cannot be validated)
|
||||
assert!(!comment_contains_code("# language=PytHoN", &[]));
|
||||
assert!(!comment_contains_code("# language=jaVaScrIpt", &[]));
|
||||
|
||||
// Space within ID (fine because `Shell` is considered the ID)
|
||||
assert!(!comment_contains_code("# language=Shell Script", &[]));
|
||||
|
||||
// With prefix and/or suffix
|
||||
assert!(!comment_contains_code("# language=HTML prefix=<body>", &[]));
|
||||
assert!(!comment_contains_code("# language=xml", &[]));
|
||||
assert!(!comment_contains_code(
|
||||
r"# language=Requirements suffix=\n",
|
||||
"# language=HTML prefix=<body> suffix=</body>",
|
||||
&[]
|
||||
));
|
||||
assert!(!comment_contains_code(
|
||||
"language=javascript prefix=(function(){ suffix=})()",
|
||||
"# language=ecma script level 4",
|
||||
&[]
|
||||
));
|
||||
}
|
||||
|
||||
@@ -469,7 +469,7 @@ impl Violation for MissingReturnTypeClassMethod {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing spec: `Any`](https://typing.readthedocs.io/en/latest/spec/special-types.html#any)
|
||||
/// - [PEP 484](https://www.python.org/dev/peps/pep-0484/#the-any-type)
|
||||
/// - [Python documentation: `typing.Any`](https://docs.python.org/3/library/typing.html#typing.Any)
|
||||
/// - [Mypy documentation: The Any type](https://mypy.readthedocs.io/en/stable/kinds_of_types.html#the-any-type)
|
||||
#[violation]
|
||||
|
||||
@@ -10,21 +10,16 @@ use crate::checkers::ast::Checker;
|
||||
use crate::registry::AsRule;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for imports of the `telnetlib` module.
|
||||
/// Checks for imports of the`telnetlib` module.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Telnet is considered insecure. It is deprecated since version 3.11, and
|
||||
/// was removed in version 3.13. Instead, use SSH or another encrypted
|
||||
/// Telnet is considered insecure. Instead, use SSH or another encrypted
|
||||
/// protocol.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// import telnetlib
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `telnetlib` - Telnet client](https://docs.python.org/3.12/library/telnetlib.html#module-telnetlib)
|
||||
/// - [PEP 594: `telnetlib`](https://peps.python.org/pep-0594/#telnetlib)
|
||||
#[violation]
|
||||
pub struct SuspiciousTelnetlibImport;
|
||||
|
||||
@@ -46,9 +41,6 @@ impl Violation for SuspiciousTelnetlibImport {
|
||||
/// ```python
|
||||
/// import ftplib
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `ftplib` - FTP protocol client](https://docs.python.org/3/library/ftplib.html)
|
||||
#[violation]
|
||||
pub struct SuspiciousFtplibImport;
|
||||
|
||||
@@ -71,9 +63,8 @@ impl Violation for SuspiciousFtplibImport {
|
||||
/// ```python
|
||||
/// import pickle
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `pickle` — Python object serialization](https://docs.python.org/3/library/pickle.html)
|
||||
/// /// ## References
|
||||
/// - [Python Docs](https://docs.python.org/3/library/pickle.html)
|
||||
#[violation]
|
||||
pub struct SuspiciousPickleImport;
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ use ruff_text_size::Ranged;
|
||||
///
|
||||
/// ## References
|
||||
/// - [Common Weakness Enumeration: CWE-22](https://cwe.mitre.org/data/definitions/22.html)
|
||||
/// - [Python documentation: `TarFile.extractall`](https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall)
|
||||
/// - [Python documentation: Extraction filters](https://docs.python.org/3/library/tarfile.html#tarfile-extraction-filter)
|
||||
/// - [Python Documentation: `TarFile.extractall`](https://docs.python.org/3/library/tarfile.html#tarfile.TarFile.extractall)
|
||||
/// - [Python Documentation: Extraction filters](https://docs.python.org/3/library/tarfile.html#tarfile-extraction-filter)
|
||||
///
|
||||
/// [PEP 706]: https://peps.python.org/pep-0706/#backporting-forward-compatibility
|
||||
#[violation]
|
||||
|
||||
@@ -60,7 +60,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ## References
|
||||
/// - [Python documentation: The `try` statement](https://docs.python.org/3/reference/compound_stmts.html#the-try-statement)
|
||||
/// - [Python documentation: Exception hierarchy](https://docs.python.org/3/library/exceptions.html#exception-hierarchy)
|
||||
/// - [PEP 8: Programming Recommendations on bare `except`](https://peps.python.org/pep-0008/#programming-recommendations)
|
||||
/// - [PEP8 Programming Recommendations on bare `except`](https://peps.python.org/pep-0008/#programming-recommendations)
|
||||
#[violation]
|
||||
pub struct BlindExcept {
|
||||
name: String,
|
||||
|
||||
@@ -88,7 +88,7 @@ impl Violation for AbstractBaseClassWithoutAbstractMethod {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `abc`](https://docs.python.org/3/library/abc.html)
|
||||
/// - [Python documentation: abc](https://docs.python.org/3/library/abc.html)
|
||||
#[violation]
|
||||
pub struct EmptyMethodWithoutAbstractDecorator {
|
||||
name: String,
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 257 – Docstring Conventions](https://peps.python.org/pep-0257/)
|
||||
/// - [PEP 257](https://peps.python.org/pep-0257/)
|
||||
/// - [Python documentation: Formatted string literals](https://docs.python.org/3/reference/lexical_analysis.html#f-strings)
|
||||
#[violation]
|
||||
pub struct FStringDocstring;
|
||||
|
||||
@@ -40,7 +40,7 @@ use crate::checkers::ast::Checker;
|
||||
///
|
||||
/// ## References
|
||||
/// - [The Hitchhiker's Guide to Python: Late Binding Closures](https://docs.python-guide.org/writing/gotchas/#late-binding-closures)
|
||||
/// - [Python documentation: `functools.partial`](https://docs.python.org/3/library/functools.html#functools.partial)
|
||||
/// - [Python documentation: functools.partial](https://docs.python.org/3/library/functools.html#functools.partial)
|
||||
#[violation]
|
||||
pub struct FunctionUsesLoopVariable {
|
||||
name: String,
|
||||
|
||||
@@ -26,9 +26,6 @@ use crate::checkers::ast::Checker;
|
||||
/// ```python
|
||||
/// warnings.warn("This is a warning", stacklevel=2)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `warnings.warn`](https://docs.python.org/3/library/warnings.html#warnings.warn)
|
||||
#[violation]
|
||||
pub struct NoExplicitStacklevel;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `contextlib.suppress`](https://docs.python.org/3/library/contextlib.html#contextlib.suppress)
|
||||
/// - [Python documentation: contextlib.suppress](https://docs.python.org/3/library/contextlib.html#contextlib.suppress)
|
||||
#[violation]
|
||||
pub struct UselessContextlibSuppress;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use ruff_diagnostics::Diagnostic;
|
||||
use ruff_diagnostics::Violation;
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::{Expr, Parameter};
|
||||
use ruff_python_ast::Parameter;
|
||||
use ruff_python_semantic::analyze::visibility::{is_overload, is_override};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
@@ -58,7 +58,7 @@ impl Violation for BuiltinArgumentShadowing {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let BuiltinArgumentShadowing { name } = self;
|
||||
format!("Function argument `{name}` is shadowing a Python builtin")
|
||||
format!("Argument `{name}` is shadowing a Python builtin")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,15 +70,6 @@ pub(crate) fn builtin_argument_shadowing(checker: &mut Checker, parameter: &Para
|
||||
&checker.settings.flake8_builtins.builtins_ignorelist,
|
||||
checker.settings.target_version,
|
||||
) {
|
||||
// Ignore parameters in lambda expressions.
|
||||
// (That is the domain of A006.)
|
||||
if checker
|
||||
.semantic()
|
||||
.current_expression()
|
||||
.is_some_and(Expr::is_lambda_expr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Ignore `@override` and `@overload` decorated functions.
|
||||
if checker
|
||||
.semantic()
|
||||
|
||||
@@ -1,58 +1,66 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A002.py:1:11: A002 Function argument `str` is shadowing a Python builtin
|
||||
A002.py:1:11: A002 Argument `str` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:1:19: A002 Function argument `type` is shadowing a Python builtin
|
||||
A002.py:1:19: A002 Argument `type` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:1:26: A002 Function argument `complex` is shadowing a Python builtin
|
||||
A002.py:1:26: A002 Argument `complex` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^^^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:1:35: A002 Function argument `Exception` is shadowing a Python builtin
|
||||
A002.py:1:35: A002 Argument `Exception` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^^^^^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:1:48: A002 Function argument `getattr` is shadowing a Python builtin
|
||||
A002.py:1:48: A002 Argument `getattr` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^^^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:5:17: A002 Function argument `bytes` is shadowing a Python builtin
|
||||
A002.py:5:17: A002 Argument `bytes` is shadowing a Python builtin
|
||||
|
|
||||
5 | async def func2(bytes):
|
||||
| ^^^^^ A002
|
||||
6 | pass
|
||||
|
|
||||
|
||||
A002.py:9:17: A002 Function argument `id` is shadowing a Python builtin
|
||||
A002.py:9:17: A002 Argument `id` is shadowing a Python builtin
|
||||
|
|
||||
9 | async def func3(id, dir):
|
||||
| ^^ A002
|
||||
10 | pass
|
||||
|
|
||||
|
||||
A002.py:9:21: A002 Function argument `dir` is shadowing a Python builtin
|
||||
A002.py:9:21: A002 Argument `dir` is shadowing a Python builtin
|
||||
|
|
||||
9 | async def func3(id, dir):
|
||||
| ^^^ A002
|
||||
10 | pass
|
||||
|
|
||||
|
||||
A002.py:13:16: A002 Argument `float` is shadowing a Python builtin
|
||||
|
|
||||
13 | map([], lambda float: ...)
|
||||
| ^^^^^ A002
|
||||
14 |
|
||||
15 | from typing import override, overload
|
||||
|
|
||||
|
||||
@@ -1,44 +1,52 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_builtins/mod.rs
|
||||
---
|
||||
A002.py:1:11: A002 Function argument `str` is shadowing a Python builtin
|
||||
A002.py:1:11: A002 Argument `str` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:1:19: A002 Function argument `type` is shadowing a Python builtin
|
||||
A002.py:1:19: A002 Argument `type` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:1:26: A002 Function argument `complex` is shadowing a Python builtin
|
||||
A002.py:1:26: A002 Argument `complex` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^^^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:1:35: A002 Function argument `Exception` is shadowing a Python builtin
|
||||
A002.py:1:35: A002 Argument `Exception` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^^^^^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:1:48: A002 Function argument `getattr` is shadowing a Python builtin
|
||||
A002.py:1:48: A002 Argument `getattr` is shadowing a Python builtin
|
||||
|
|
||||
1 | def func1(str, /, type, *complex, Exception, **getattr):
|
||||
| ^^^^^^^ A002
|
||||
2 | pass
|
||||
|
|
||||
|
||||
A002.py:5:17: A002 Function argument `bytes` is shadowing a Python builtin
|
||||
A002.py:5:17: A002 Argument `bytes` is shadowing a Python builtin
|
||||
|
|
||||
5 | async def func2(bytes):
|
||||
| ^^^^^ A002
|
||||
6 | pass
|
||||
|
|
||||
|
||||
A002.py:13:16: A002 Argument `float` is shadowing a Python builtin
|
||||
|
|
||||
13 | map([], lambda float: ...)
|
||||
| ^^^^^ A002
|
||||
14 |
|
||||
15 | from typing import override, overload
|
||||
|
|
||||
|
||||
@@ -61,6 +61,4 @@ A006.py:5:8: A006 Lambda argument `dir` is shadowing a Python builtin
|
||||
4 | lambda id: id
|
||||
5 | lambda dir: dir
|
||||
| ^^^ A006
|
||||
6 |
|
||||
7 | # Ok for A006 - should trigger A002 instead
|
||||
|
|
||||
|
||||
@@ -30,9 +30,6 @@ use crate::checkers::ast::Checker;
|
||||
/// dict.fromkeys(iterable)
|
||||
/// dict.fromkeys(iterable, 1)
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `dict.fromkeys`](https://docs.python.org/3/library/stdtypes.html#dict.fromkeys)
|
||||
#[violation]
|
||||
pub struct UnnecessaryDictComprehensionForIterable {
|
||||
is_value_none_literal: bool,
|
||||
@@ -56,7 +53,7 @@ impl Violation for UnnecessaryDictComprehensionForIterable {
|
||||
}
|
||||
}
|
||||
|
||||
/// C420
|
||||
/// RUF025
|
||||
pub(crate) fn unnecessary_dict_comprehension_for_iterable(
|
||||
checker: &mut Checker,
|
||||
dict_comp: &ast::ExprDictComp,
|
||||
|
||||
@@ -39,7 +39,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct FStringInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct FormatInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ use ruff_text_size::Ranged;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `gettext` — Multilingual internationalization services](https://docs.python.org/3/library/gettext.html)
|
||||
/// - [Python documentation: gettext](https://docs.python.org/3/library/gettext.html)
|
||||
#[violation]
|
||||
pub struct PrintfInGetTextFuncCall;
|
||||
|
||||
|
||||
@@ -432,7 +432,7 @@ impl AlwaysFixableViolation for LoggingWarn {
|
||||
///
|
||||
/// username = "Maria"
|
||||
///
|
||||
/// logging.info("Something happened", extra=dict(user_id=username))
|
||||
/// logging.info("Something happened", extra=dict(user=username))
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
|
||||
@@ -22,7 +22,7 @@ use crate::Locator;
|
||||
///
|
||||
/// Directories that lack an `__init__.py` file can still be imported, but
|
||||
/// they're indicative of a special kind of package, known as a "namespace
|
||||
/// package" (see: [PEP 420](https://peps.python.org/pep-0420/)).
|
||||
/// package" (see: [PEP 420](https://www.python.org/dev/peps/pep-0420/)).
|
||||
/// Namespace packages are less widely used, so a package that lacks an
|
||||
/// `__init__.py` file is typically meant to be a regular package, and
|
||||
/// the absence of the `__init__.py` file is probably an oversight.
|
||||
|
||||
@@ -30,7 +30,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation: Version and platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
|
||||
/// The [typing documentation on stub files](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
|
||||
#[violation]
|
||||
pub struct ComplexIfStatementInStub;
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use ruff_python_ast::ExprStringLiteral;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use ruff_python_semantic::Definition;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
@@ -31,40 +29,18 @@ use crate::checkers::ast::Checker;
|
||||
#[violation]
|
||||
pub struct DocstringInStub;
|
||||
|
||||
impl AlwaysFixableViolation for DocstringInStub {
|
||||
impl Violation for DocstringInStub {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
"Docstrings should not be included in stubs".to_string()
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove docstring".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI021
|
||||
pub(crate) fn docstring_in_stubs(
|
||||
checker: &mut Checker,
|
||||
definition: &Definition,
|
||||
docstring: Option<&ExprStringLiteral>,
|
||||
) {
|
||||
let Some(docstring_range) = docstring.map(ExprStringLiteral::range) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let statements = match definition {
|
||||
Definition::Module(module) => module.python_ast,
|
||||
Definition::Member(member) => member.body(),
|
||||
};
|
||||
|
||||
let edit = if statements.len() == 1 {
|
||||
Edit::range_replacement("...".to_string(), docstring_range)
|
||||
} else {
|
||||
Edit::range_deletion(docstring_range)
|
||||
};
|
||||
|
||||
let fix = Fix::unsafe_edit(edit);
|
||||
let diagnostic = Diagnostic::new(DocstringInStub, docstring_range).with_fix(fix);
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
pub(crate) fn docstring_in_stubs(checker: &mut Checker, docstring: Option<&ExprStringLiteral>) {
|
||||
if let Some(docstr) = docstring {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(DocstringInStub, docstr.range()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ use std::collections::HashSet;
|
||||
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use ruff_diagnostics::{Diagnostic, FixAvailability, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::comparable::ComparableExpr;
|
||||
use ruff_python_ast::{self as ast, Expr, ExprContext};
|
||||
use ruff_python_ast::Expr;
|
||||
use ruff_python_semantic::analyze::typing::traverse_literal;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
use ruff_text_size::Ranged;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
@@ -27,10 +27,6 @@ use crate::checkers::ast::Checker;
|
||||
/// foo: Literal["a", "b"]
|
||||
/// ```
|
||||
///
|
||||
/// ## Fix safety
|
||||
/// This rule's fix is marked as safe; however, the fix will flatten nested
|
||||
/// literals into a single top-level literal.
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal)
|
||||
#[violation]
|
||||
@@ -38,29 +34,24 @@ pub struct DuplicateLiteralMember {
|
||||
duplicate_name: String,
|
||||
}
|
||||
|
||||
impl AlwaysFixableViolation for DuplicateLiteralMember {
|
||||
impl Violation for DuplicateLiteralMember {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
format!("Duplicate literal member `{}`", self.duplicate_name)
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> String {
|
||||
"Remove duplicates".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI062
|
||||
pub(crate) fn duplicate_literal_member<'a>(checker: &mut Checker, expr: &'a Expr) {
|
||||
let mut seen_nodes: HashSet<ComparableExpr<'_>, _> = FxHashSet::default();
|
||||
let mut unique_nodes: Vec<&Expr> = Vec::new();
|
||||
let mut diagnostics: Vec<Diagnostic> = Vec::new();
|
||||
|
||||
// Adds a member to `literal_exprs` if it is a `Literal` annotation
|
||||
let mut check_for_duplicate_members = |expr: &'a Expr, _: &'a Expr| {
|
||||
// If we've already seen this literal member, raise a violation.
|
||||
if seen_nodes.insert(expr.into()) {
|
||||
unique_nodes.push(expr);
|
||||
} else {
|
||||
if !seen_nodes.insert(expr.into()) {
|
||||
diagnostics.push(Diagnostic::new(
|
||||
DuplicateLiteralMember {
|
||||
duplicate_name: checker.generator().expr(expr),
|
||||
@@ -70,36 +61,7 @@ pub(crate) fn duplicate_literal_member<'a>(checker: &mut Checker, expr: &'a Expr
|
||||
}
|
||||
};
|
||||
|
||||
// Traverse the literal, collect all diagnostic members.
|
||||
// Traverse the literal, collect all diagnostic members
|
||||
traverse_literal(&mut check_for_duplicate_members, checker.semantic(), expr);
|
||||
|
||||
// If there's at least one diagnostic, create a fix to remove the duplicate members.
|
||||
if !diagnostics.is_empty() {
|
||||
if let Expr::Subscript(subscript) = expr {
|
||||
let subscript = Expr::Subscript(ast::ExprSubscript {
|
||||
slice: Box::new(if let [elt] = unique_nodes.as_slice() {
|
||||
(*elt).clone()
|
||||
} else {
|
||||
Expr::Tuple(ast::ExprTuple {
|
||||
elts: unique_nodes.into_iter().cloned().collect(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
parenthesized: false,
|
||||
})
|
||||
}),
|
||||
value: subscript.value.clone(),
|
||||
range: TextRange::default(),
|
||||
ctx: ExprContext::Load,
|
||||
});
|
||||
let fix = Fix::safe_edit(Edit::range_replacement(
|
||||
checker.generator().expr(&subscript),
|
||||
expr.range(),
|
||||
));
|
||||
for diagnostic in &mut diagnostics {
|
||||
diagnostic.set_fix(fix.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checker.diagnostics.append(&mut diagnostics);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation - Writing and Maintaining Stub Files](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html)
|
||||
/// - [The recommended style for stub functions and methods](https://typing.readthedocs.io/en/latest/source/stubs.html#id6)
|
||||
/// in the typing docs.
|
||||
#[violation]
|
||||
pub struct NonEmptyStubBody;
|
||||
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use ruff_python_ast::{self as ast, Decorator, Expr, Parameters, Stmt};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::importer::ImportRequest;
|
||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::helpers::map_subscript;
|
||||
use ruff_python_ast::identifier::Identifier;
|
||||
use ruff_python_semantic::analyze;
|
||||
use ruff_python_semantic::analyze::visibility::{is_abstract, is_final, is_overload};
|
||||
use ruff_python_semantic::{ScopeKind, SemanticModel};
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for methods that are annotated with a fixed return type which
|
||||
@@ -72,7 +71,7 @@ use ruff_text_size::{Ranged, TextRange};
|
||||
/// def __iadd__(self, other: Foo) -> Self: ...
|
||||
/// ```
|
||||
/// ## References
|
||||
/// - [Python documentation: `typing.Self`](https://docs.python.org/3/library/typing.html#typing.Self)
|
||||
/// - [`typing.Self` documentation](https://docs.python.org/3/library/typing.html#typing.Self)
|
||||
#[violation]
|
||||
pub struct NonSelfReturnType {
|
||||
class_name: String,
|
||||
@@ -80,15 +79,12 @@ pub struct NonSelfReturnType {
|
||||
}
|
||||
|
||||
impl Violation for NonSelfReturnType {
|
||||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let NonSelfReturnType {
|
||||
class_name,
|
||||
method_name,
|
||||
} = self;
|
||||
|
||||
if matches!(class_name.as_str(), "__new__") {
|
||||
"`__new__` methods usually return `self` at runtime".to_string()
|
||||
} else {
|
||||
@@ -97,7 +93,7 @@ impl Violation for NonSelfReturnType {
|
||||
}
|
||||
|
||||
fn fix_title(&self) -> Option<String> {
|
||||
Some("Use `Self` as return type".to_string())
|
||||
Some("Consider using `typing_extensions.Self` as return type".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +136,13 @@ pub(crate) fn non_self_return_type(
|
||||
&& is_name(returns, &class_def.name)
|
||||
&& !is_final(&class_def.decorator_list, semantic)
|
||||
{
|
||||
add_diagnostic(checker, stmt, returns, class_def, name);
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -148,7 +150,13 @@ pub(crate) fn non_self_return_type(
|
||||
// In-place methods that are expected to return `Self`.
|
||||
if is_inplace_bin_op(name) {
|
||||
if !is_self(returns, checker) {
|
||||
add_diagnostic(checker, stmt, returns, class_def, name);
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -156,7 +164,13 @@ pub(crate) fn non_self_return_type(
|
||||
if is_name(returns, &class_def.name) {
|
||||
if matches!(name, "__enter__" | "__new__") && !is_final(&class_def.decorator_list, semantic)
|
||||
{
|
||||
add_diagnostic(checker, stmt, returns, class_def, name);
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -166,68 +180,32 @@ pub(crate) fn non_self_return_type(
|
||||
if is_iterable_or_iterator(returns, semantic)
|
||||
&& subclasses_iterator(class_def, semantic)
|
||||
{
|
||||
add_diagnostic(checker, stmt, returns, class_def, name);
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
"__aiter__" => {
|
||||
if is_async_iterable_or_iterator(returns, semantic)
|
||||
&& subclasses_async_iterator(class_def, semantic)
|
||||
{
|
||||
add_diagnostic(checker, stmt, returns, class_def, name);
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: name.to_string(),
|
||||
},
|
||||
stmt.identifier(),
|
||||
));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a diagnostic for the given method.
|
||||
fn add_diagnostic(
|
||||
checker: &mut Checker,
|
||||
stmt: &Stmt,
|
||||
returns: &Expr,
|
||||
class_def: &ast::StmtClassDef,
|
||||
method_name: &str,
|
||||
) {
|
||||
/// Return an [`Edit`] that imports `typing.Self` from `typing` or `typing_extensions`.
|
||||
fn import_self(checker: &Checker, range: TextRange) -> Option<Edit> {
|
||||
let target_version = checker.settings.target_version.as_tuple();
|
||||
let source_module = if checker.source_type.is_stub() || target_version >= (3, 11) {
|
||||
"typing"
|
||||
} else {
|
||||
"typing_extensions"
|
||||
};
|
||||
|
||||
let (importer, semantic) = (checker.importer(), checker.semantic());
|
||||
let request = ImportRequest::import_from(source_module, "Self");
|
||||
|
||||
let Ok((edit, ..)) = importer.get_or_import_symbol(&request, range.start(), semantic)
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(edit)
|
||||
}
|
||||
|
||||
/// Generate a [`Fix`] that replaces the return type with `Self`.
|
||||
fn replace_with_self(checker: &mut Checker, range: TextRange) -> Option<Fix> {
|
||||
let import_self = import_self(checker, range)?;
|
||||
let replace_with_self = Edit::range_replacement("Self".to_string(), range);
|
||||
Some(Fix::unsafe_edits(import_self, [replace_with_self]))
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(
|
||||
NonSelfReturnType {
|
||||
class_name: class_def.name.to_string(),
|
||||
method_name: method_name.to_string(),
|
||||
},
|
||||
stmt.identifier(),
|
||||
);
|
||||
if let Some(fix) = replace_with_self(checker, returns.range()) {
|
||||
diagnostic.set_fix(fix);
|
||||
}
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
/// Returns `true` if the method is an in-place binary operator.
|
||||
fn is_inplace_bin_op(name: &str) -> bool {
|
||||
matches!(
|
||||
|
||||
@@ -23,7 +23,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation - Writing and Maintaining Stub Files](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html)
|
||||
/// The [recommended style for functions and methods](https://typing.readthedocs.io/en/latest/source/stubs.html#functions-and-methods)
|
||||
/// in the typing docs.
|
||||
#[violation]
|
||||
pub struct PassStatementStubBody;
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation - Writing and Maintaining Stub Files](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html)
|
||||
/// - [Static Typing with Python: Type Stubs](https://typing.readthedocs.io/en/latest/source/stubs.html)
|
||||
#[violation]
|
||||
pub struct QuotedAnnotationInStub;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: The numeric tower](https://docs.python.org/3/library/numbers.html#the-numeric-tower)
|
||||
/// - [The typing specification](https://docs.python.org/3/library/numbers.html#the-numeric-tower)
|
||||
/// - [PEP 484: The numeric tower](https://peps.python.org/pep-0484/#the-numeric-tower)
|
||||
///
|
||||
/// [typing specification]: https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex
|
||||
|
||||
@@ -40,7 +40,7 @@ use crate::registry::Rule;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
|
||||
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
|
||||
#[violation]
|
||||
pub struct UnrecognizedPlatformCheck;
|
||||
|
||||
@@ -74,7 +74,7 @@ impl Violation for UnrecognizedPlatformCheck {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
|
||||
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
|
||||
#[violation]
|
||||
pub struct UnrecognizedPlatformName {
|
||||
platform: String,
|
||||
|
||||
@@ -31,7 +31,7 @@ use crate::registry::Rule;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
|
||||
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
|
||||
#[violation]
|
||||
pub struct UnrecognizedVersionInfoCheck;
|
||||
|
||||
@@ -70,7 +70,7 @@ impl Violation for UnrecognizedVersionInfoCheck {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
|
||||
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
|
||||
#[violation]
|
||||
pub struct PatchVersionComparison;
|
||||
|
||||
@@ -106,7 +106,7 @@ impl Violation for PatchVersionComparison {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [Typing documentation: Version and Platform checking](https://typing.readthedocs.io/en/latest/spec/directives.html#version-and-platform-checks)
|
||||
/// - [Typing stubs documentation: Version and Platform Checks](https://typing.readthedocs.io/en/latest/source/stubs.html#version-and-platform-checks)
|
||||
#[violation]
|
||||
pub struct WrongTupleLengthVersionComparison {
|
||||
expected_length: usize,
|
||||
|
||||
@@ -1,24 +1,15 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PYI021.pyi:1:1: PYI021 [*] Docstrings should not be included in stubs
|
||||
PYI021.pyi:1:1: PYI021 Docstrings should not be included in stubs
|
||||
|
|
||||
1 | """foo""" # ERROR PYI021
|
||||
| ^^^^^^^^^ PYI021
|
||||
2 |
|
||||
3 | def foo():
|
||||
|
|
||||
= help: Remove docstring
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 |-"""foo""" # ERROR PYI021
|
||||
1 |+ # ERROR PYI021
|
||||
2 2 |
|
||||
3 3 | def foo():
|
||||
4 4 | """foo""" # ERROR PYI021
|
||||
|
||||
PYI021.pyi:4:5: PYI021 [*] Docstrings should not be included in stubs
|
||||
PYI021.pyi:4:5: PYI021 Docstrings should not be included in stubs
|
||||
|
|
||||
3 | def foo():
|
||||
4 | """foo""" # ERROR PYI021
|
||||
@@ -26,81 +17,14 @@ PYI021.pyi:4:5: PYI021 [*] Docstrings should not be included in stubs
|
||||
5 |
|
||||
6 | class Bar:
|
||||
|
|
||||
= help: Remove docstring
|
||||
|
||||
ℹ Unsafe fix
|
||||
1 1 | """foo""" # ERROR PYI021
|
||||
2 2 |
|
||||
3 3 | def foo():
|
||||
4 |- """foo""" # ERROR PYI021
|
||||
4 |+ ... # ERROR PYI021
|
||||
5 5 |
|
||||
6 6 | class Bar:
|
||||
7 7 | """bar""" # ERROR PYI021
|
||||
|
||||
PYI021.pyi:7:5: PYI021 [*] Docstrings should not be included in stubs
|
||||
PYI021.pyi:7:5: PYI021 Docstrings should not be included in stubs
|
||||
|
|
||||
6 | class Bar:
|
||||
7 | """bar""" # ERROR PYI021
|
||||
| ^^^^^^^^^ PYI021
|
||||
8 |
|
||||
9 | class Qux:
|
||||
9 | def bar():
|
||||
|
|
||||
= help: Remove docstring
|
||||
|
||||
ℹ Unsafe fix
|
||||
4 4 | """foo""" # ERROR PYI021
|
||||
5 5 |
|
||||
6 6 | class Bar:
|
||||
7 |- """bar""" # ERROR PYI021
|
||||
7 |+ ... # ERROR PYI021
|
||||
8 8 |
|
||||
9 9 | class Qux:
|
||||
10 10 | """qux""" # ERROR PYI021
|
||||
|
||||
PYI021.pyi:10:5: PYI021 [*] Docstrings should not be included in stubs
|
||||
|
|
||||
9 | class Qux:
|
||||
10 | """qux""" # ERROR PYI021
|
||||
| ^^^^^^^^^ PYI021
|
||||
11 |
|
||||
12 | def __init__(self) -> None: ...
|
||||
|
|
||||
= help: Remove docstring
|
||||
|
||||
ℹ Unsafe fix
|
||||
7 7 | """bar""" # ERROR PYI021
|
||||
8 8 |
|
||||
9 9 | class Qux:
|
||||
10 |- """qux""" # ERROR PYI021
|
||||
10 |+ # ERROR PYI021
|
||||
11 11 |
|
||||
12 12 | def __init__(self) -> None: ...
|
||||
13 13 |
|
||||
|
||||
PYI021.pyi:15:5: PYI021 [*] Docstrings should not be included in stubs
|
||||
|
|
||||
14 | class Baz:
|
||||
15 | """Multiline docstring
|
||||
| _____^
|
||||
16 | |
|
||||
17 | | Lorem ipsum dolor sit amet
|
||||
18 | | """
|
||||
| |_______^ PYI021
|
||||
19 |
|
||||
20 | def __init__(self) -> None: ...
|
||||
|
|
||||
= help: Remove docstring
|
||||
|
||||
ℹ Unsafe fix
|
||||
12 12 | def __init__(self) -> None: ...
|
||||
13 13 |
|
||||
14 14 | class Baz:
|
||||
15 |- """Multiline docstring
|
||||
16 |-
|
||||
17 |- Lorem ipsum dolor sit amet
|
||||
18 |- """
|
||||
15 |+
|
||||
19 16 |
|
||||
20 17 | def __init__(self) -> None: ...
|
||||
21 18 |
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PYI034.py:21:9: PYI034 [*] `__new__` methods in classes like `Bad` usually return `self` at runtime
|
||||
PYI034.py:21:9: PYI034 `__new__` methods in classes like `Bad` usually return `self` at runtime
|
||||
|
|
||||
19 | object
|
||||
20 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
|
||||
@@ -10,19 +9,9 @@ PYI034.py:21:9: PYI034 [*] `__new__` methods in classes like `Bad` usually retur
|
||||
| ^^^^^^^ PYI034
|
||||
22 | ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
18 18 | class Bad(
|
||||
19 19 | object
|
||||
20 20 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
|
||||
21 |- def __new__(cls, *args: Any, **kwargs: Any) -> Bad:
|
||||
21 |+ def __new__(cls, *args: Any, **kwargs: Any) -> Self:
|
||||
22 22 | ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
||||
23 23 |
|
||||
24 24 | def __repr__(self) -> str:
|
||||
|
||||
PYI034.py:36:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually return `self` at runtime
|
||||
PYI034.py:36:9: PYI034 `__enter__` methods in classes like `Bad` usually return `self` at runtime
|
||||
|
|
||||
34 | ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
|
||||
35 |
|
||||
@@ -30,19 +19,9 @@ PYI034.py:36:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually ret
|
||||
| ^^^^^^^^^ PYI034
|
||||
37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
33 33 | def __ne__(self, other: typing.Any) -> typing.Any:
|
||||
34 34 | ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
|
||||
35 35 |
|
||||
36 |- def __enter__(self) -> Bad:
|
||||
36 |+ def __enter__(self) -> Self:
|
||||
37 37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
38 38 |
|
||||
39 39 | async def __aenter__(self) -> Bad:
|
||||
|
||||
PYI034.py:39:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually return `self` at runtime
|
||||
PYI034.py:39:15: PYI034 `__aenter__` methods in classes like `Bad` usually return `self` at runtime
|
||||
|
|
||||
37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
38 |
|
||||
@@ -50,19 +29,9 @@ PYI034.py:39:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually r
|
||||
| ^^^^^^^^^^ PYI034
|
||||
40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
36 36 | def __enter__(self) -> Bad:
|
||||
37 37 | ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
38 38 |
|
||||
39 |- async def __aenter__(self) -> Bad:
|
||||
39 |+ async def __aenter__(self) -> Self:
|
||||
40 40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
41 41 |
|
||||
42 42 | def __iadd__(self, other: Bad) -> Bad:
|
||||
|
||||
PYI034.py:42:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually return `self` at runtime
|
||||
PYI034.py:42:9: PYI034 `__iadd__` methods in classes like `Bad` usually return `self` at runtime
|
||||
|
|
||||
40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
41 |
|
||||
@@ -70,38 +39,18 @@ PYI034.py:42:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually retu
|
||||
| ^^^^^^^^ PYI034
|
||||
43 | ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
39 39 | async def __aenter__(self) -> Bad:
|
||||
40 40 | ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
41 41 |
|
||||
42 |- def __iadd__(self, other: Bad) -> Bad:
|
||||
42 |+ def __iadd__(self, other: Bad) -> Self:
|
||||
43 43 | ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
||||
44 44 |
|
||||
45 45 |
|
||||
|
||||
PYI034.py:165:9: PYI034 [*] `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
|
||||
PYI034.py:165:9: PYI034 `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
|
||||
|
|
||||
164 | class BadIterator1(Iterator[int]):
|
||||
165 | def __iter__(self) -> Iterator[int]:
|
||||
| ^^^^^^^^ PYI034
|
||||
166 | ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
162 162 |
|
||||
163 163 |
|
||||
164 164 | class BadIterator1(Iterator[int]):
|
||||
165 |- def __iter__(self) -> Iterator[int]:
|
||||
165 |+ def __iter__(self) -> Self:
|
||||
166 166 | ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
167 167 |
|
||||
168 168 |
|
||||
|
||||
PYI034.py:172:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
|
||||
PYI034.py:172:9: PYI034 `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
|
||||
|
|
||||
170 | typing.Iterator[int]
|
||||
171 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
@@ -109,19 +58,9 @@ PYI034.py:172:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` us
|
||||
| ^^^^^^^^ PYI034
|
||||
173 | ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
169 169 | class BadIterator2(
|
||||
170 170 | typing.Iterator[int]
|
||||
171 171 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
172 |- def __iter__(self) -> Iterator[int]:
|
||||
172 |+ def __iter__(self) -> Self:
|
||||
173 173 | ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
174 174 |
|
||||
175 175 |
|
||||
|
||||
PYI034.py:179:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
|
||||
PYI034.py:179:9: PYI034 `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
|
||||
|
|
||||
177 | typing.Iterator[int]
|
||||
178 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
@@ -129,19 +68,9 @@ PYI034.py:179:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` us
|
||||
| ^^^^^^^^ PYI034
|
||||
180 | ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
176 176 | class BadIterator3(
|
||||
177 177 | typing.Iterator[int]
|
||||
178 178 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
179 |- def __iter__(self) -> collections.abc.Iterator[int]:
|
||||
179 |+ def __iter__(self) -> Self:
|
||||
180 180 | ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
181 181 |
|
||||
182 182 |
|
||||
|
||||
PYI034.py:185:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
|
||||
PYI034.py:185:9: PYI034 `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
|
||||
|
|
||||
183 | class BadIterator4(Iterator[int]):
|
||||
184 | # Note: *Iterable*, not *Iterator*, returned!
|
||||
@@ -149,71 +78,31 @@ PYI034.py:185:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` us
|
||||
| ^^^^^^^^ PYI034
|
||||
186 | ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
182 182 |
|
||||
183 183 | class BadIterator4(Iterator[int]):
|
||||
184 184 | # Note: *Iterable*, not *Iterator*, returned!
|
||||
185 |- def __iter__(self) -> Iterable[int]:
|
||||
185 |+ def __iter__(self) -> Self:
|
||||
186 186 | ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
187 187 |
|
||||
188 188 |
|
||||
|
||||
PYI034.py:195:9: PYI034 [*] `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
|
||||
PYI034.py:195:9: PYI034 `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
|
||||
|
|
||||
194 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
|
||||
195 | def __aiter__(self) -> typing.AsyncIterator[str]:
|
||||
| ^^^^^^^^^ PYI034
|
||||
196 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
192 192 |
|
||||
193 193 |
|
||||
194 194 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
|
||||
195 |- def __aiter__(self) -> typing.AsyncIterator[str]:
|
||||
195 |+ def __aiter__(self) -> Self:
|
||||
196 196 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
197 197 |
|
||||
198 198 | class SubclassOfBadIterator3(BadIterator3):
|
||||
|
||||
PYI034.py:199:9: PYI034 [*] `__iter__` methods in classes like `SubclassOfBadIterator3` usually return `self` at runtime
|
||||
PYI034.py:199:9: PYI034 `__iter__` methods in classes like `SubclassOfBadIterator3` usually return `self` at runtime
|
||||
|
|
||||
198 | class SubclassOfBadIterator3(BadIterator3):
|
||||
199 | def __iter__(self) -> Iterator[int]: # Y034
|
||||
| ^^^^^^^^ PYI034
|
||||
200 | ...
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
196 196 | ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
197 197 |
|
||||
198 198 | class SubclassOfBadIterator3(BadIterator3):
|
||||
199 |- def __iter__(self) -> Iterator[int]: # Y034
|
||||
199 |+ def __iter__(self) -> Self: # Y034
|
||||
200 200 | ...
|
||||
201 201 |
|
||||
202 202 | class SubclassOfBadAsyncIterator(BadAsyncIterator):
|
||||
|
||||
PYI034.py:203:9: PYI034 [*] `__aiter__` methods in classes like `SubclassOfBadAsyncIterator` usually return `self` at runtime
|
||||
PYI034.py:203:9: PYI034 `__aiter__` methods in classes like `SubclassOfBadAsyncIterator` usually return `self` at runtime
|
||||
|
|
||||
202 | class SubclassOfBadAsyncIterator(BadAsyncIterator):
|
||||
203 | def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
|
||||
| ^^^^^^^^^ PYI034
|
||||
204 | ...
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
200 200 | ...
|
||||
201 201 |
|
||||
202 202 | class SubclassOfBadAsyncIterator(BadAsyncIterator):
|
||||
203 |- def __aiter__(self) -> collections.abc.AsyncIterator[str]: # Y034
|
||||
203 |+ def __aiter__(self) -> Self: # Y034
|
||||
204 204 | ...
|
||||
205 205 |
|
||||
206 206 | class AsyncIteratorReturningAsyncIterable:
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PYI034.pyi:20:9: PYI034 [*] `__new__` methods in classes like `Bad` usually return `self` at runtime
|
||||
PYI034.pyi:20:9: PYI034 `__new__` methods in classes like `Bad` usually return `self` at runtime
|
||||
|
|
||||
18 | object
|
||||
19 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
|
||||
@@ -11,19 +10,9 @@ PYI034.pyi:20:9: PYI034 [*] `__new__` methods in classes like `Bad` usually retu
|
||||
21 | cls, *args: Any, **kwargs: Any
|
||||
22 | ) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
19 19 | ): # Y040 Do not inherit from "object" explicitly, as it is redundant in Python 3
|
||||
20 20 | def __new__(
|
||||
21 21 | cls, *args: Any, **kwargs: Any
|
||||
22 |- ) -> Bad: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
||||
22 |+ ) -> Self: ... # Y034 "__new__" methods usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__new__", e.g. "def __new__(cls, *args: Any, **kwargs: Any) -> Self: ..."
|
||||
23 23 | def __repr__(
|
||||
24 24 | self,
|
||||
25 25 | ) -> str: ... # Y029 Defining __repr__ or __str__ in a stub is almost always redundant
|
||||
|
||||
PYI034.pyi:35:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually return `self` at runtime
|
||||
PYI034.pyi:35:9: PYI034 `__enter__` methods in classes like `Bad` usually return `self` at runtime
|
||||
|
|
||||
33 | self, other: typing.Any
|
||||
34 | ) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
|
||||
@@ -32,19 +21,9 @@ PYI034.pyi:35:9: PYI034 [*] `__enter__` methods in classes like `Bad` usually re
|
||||
36 | self,
|
||||
37 | ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
34 34 | ) -> typing.Any: ... # Y032 Prefer "object" to "Any" for the second parameter in "__ne__" methods
|
||||
35 35 | def __enter__(
|
||||
36 36 | self,
|
||||
37 |- ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
37 |+ ) -> Self: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
38 38 | async def __aenter__(
|
||||
39 39 | self,
|
||||
40 40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
|
||||
PYI034.pyi:38:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually return `self` at runtime
|
||||
PYI034.pyi:38:15: PYI034 `__aenter__` methods in classes like `Bad` usually return `self` at runtime
|
||||
|
|
||||
36 | self,
|
||||
37 | ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
@@ -53,19 +32,9 @@ PYI034.pyi:38:15: PYI034 [*] `__aenter__` methods in classes like `Bad` usually
|
||||
39 | self,
|
||||
40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
37 37 | ) -> Bad: ... # Y034 "__enter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__enter__", e.g. "def __enter__(self) -> Self: ..."
|
||||
38 38 | async def __aenter__(
|
||||
39 39 | self,
|
||||
40 |- ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
40 |+ ) -> Self: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
41 41 | def __iadd__(
|
||||
42 42 | self, other: Bad
|
||||
43 43 | ) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
||||
|
||||
PYI034.pyi:41:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually return `self` at runtime
|
||||
PYI034.pyi:41:9: PYI034 `__iadd__` methods in classes like `Bad` usually return `self` at runtime
|
||||
|
|
||||
39 | self,
|
||||
40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
@@ -74,19 +43,9 @@ PYI034.pyi:41:9: PYI034 [*] `__iadd__` methods in classes like `Bad` usually ret
|
||||
42 | self, other: Bad
|
||||
43 | ) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
40 40 | ) -> Bad: ... # Y034 "__aenter__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__aenter__", e.g. "async def __aenter__(self) -> Self: ..."
|
||||
41 41 | def __iadd__(
|
||||
42 42 | self, other: Bad
|
||||
43 |- ) -> Bad: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
||||
43 |+ ) -> Self: ... # Y034 "__iadd__" methods in classes like "Bad" usually return "self" at runtime. Consider using "typing_extensions.Self" in "Bad.__iadd__", e.g. "def __iadd__(self, other: Bad) -> Self: ..."
|
||||
44 44 |
|
||||
45 45 | class AlsoBad(
|
||||
46 46 | int, builtins.object
|
||||
|
||||
PYI034.pyi:104:9: PYI034 [*] `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
|
||||
PYI034.pyi:104:9: PYI034 `__iter__` methods in classes like `BadIterator1` usually return `self` at runtime
|
||||
|
|
||||
103 | class BadIterator1(Iterator[int]):
|
||||
104 | def __iter__(
|
||||
@@ -94,21 +53,9 @@ PYI034.pyi:104:9: PYI034 [*] `__iter__` methods in classes like `BadIterator1` u
|
||||
105 | self,
|
||||
106 | ) -> Iterator[
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
103 103 | class BadIterator1(Iterator[int]):
|
||||
104 104 | def __iter__(
|
||||
105 105 | self,
|
||||
106 |- ) -> Iterator[
|
||||
107 |- int
|
||||
108 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
106 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator1" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator1.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
109 107 |
|
||||
110 108 | class BadIterator2(
|
||||
111 109 | typing.Iterator[int]
|
||||
|
||||
PYI034.pyi:113:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
|
||||
PYI034.pyi:113:9: PYI034 `__iter__` methods in classes like `BadIterator2` usually return `self` at runtime
|
||||
|
|
||||
111 | typing.Iterator[int]
|
||||
112 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
@@ -117,21 +64,9 @@ PYI034.pyi:113:9: PYI034 [*] `__iter__` methods in classes like `BadIterator2` u
|
||||
114 | self,
|
||||
115 | ) -> Iterator[
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
112 112 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
113 113 | def __iter__(
|
||||
114 114 | self,
|
||||
115 |- ) -> Iterator[
|
||||
116 |- int
|
||||
117 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
115 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator2" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator2.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
118 116 |
|
||||
119 117 | class BadIterator3(
|
||||
120 118 | typing.Iterator[int]
|
||||
|
||||
PYI034.pyi:122:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
|
||||
PYI034.pyi:122:9: PYI034 `__iter__` methods in classes like `BadIterator3` usually return `self` at runtime
|
||||
|
|
||||
120 | typing.Iterator[int]
|
||||
121 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
@@ -140,21 +75,9 @@ PYI034.pyi:122:9: PYI034 [*] `__iter__` methods in classes like `BadIterator3` u
|
||||
123 | self,
|
||||
124 | ) -> collections.abc.Iterator[
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
121 121 | ): # Y022 Use "collections.abc.Iterator[T]" instead of "typing.Iterator[T]" (PEP 585 syntax)
|
||||
122 122 | def __iter__(
|
||||
123 123 | self,
|
||||
124 |- ) -> collections.abc.Iterator[
|
||||
125 |- int
|
||||
126 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
124 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator3" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator3.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
127 125 |
|
||||
128 126 | class BadIterator4(Iterator[int]):
|
||||
129 127 | # Note: *Iterable*, not *Iterator*, returned!
|
||||
|
||||
PYI034.pyi:130:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
|
||||
PYI034.pyi:130:9: PYI034 `__iter__` methods in classes like `BadIterator4` usually return `self` at runtime
|
||||
|
|
||||
128 | class BadIterator4(Iterator[int]):
|
||||
129 | # Note: *Iterable*, not *Iterator*, returned!
|
||||
@@ -163,21 +86,9 @@ PYI034.pyi:130:9: PYI034 [*] `__iter__` methods in classes like `BadIterator4` u
|
||||
131 | self,
|
||||
132 | ) -> Iterable[
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
ℹ Unsafe fix
|
||||
129 129 | # Note: *Iterable*, not *Iterator*, returned!
|
||||
130 130 | def __iter__(
|
||||
131 131 | self,
|
||||
132 |- ) -> Iterable[
|
||||
133 |- int
|
||||
134 |- ]: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
132 |+ ) -> Self: ... # Y034 "__iter__" methods in classes like "BadIterator4" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadIterator4.__iter__", e.g. "def __iter__(self) -> Self: ..."
|
||||
135 133 |
|
||||
136 134 | class IteratorReturningIterable:
|
||||
137 135 | def __iter__(
|
||||
|
||||
PYI034.pyi:144:9: PYI034 [*] `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
|
||||
PYI034.pyi:144:9: PYI034 `__aiter__` methods in classes like `BadAsyncIterator` usually return `self` at runtime
|
||||
|
|
||||
143 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
|
||||
144 | def __aiter__(
|
||||
@@ -185,16 +96,6 @@ PYI034.pyi:144:9: PYI034 [*] `__aiter__` methods in classes like `BadAsyncIterat
|
||||
145 | self,
|
||||
146 | ) -> typing.AsyncIterator[
|
||||
|
|
||||
= help: Use `Self` as return type
|
||||
= help: Consider using `typing_extensions.Self` as return type
|
||||
|
||||
|
||||
ℹ Unsafe fix
|
||||
143 143 | class BadAsyncIterator(collections.abc.AsyncIterator[str]):
|
||||
144 144 | def __aiter__(
|
||||
145 145 | self,
|
||||
146 |- ) -> typing.AsyncIterator[
|
||||
147 |- str
|
||||
148 |- ]: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
146 |+ ) -> Self: ... # Y034 "__aiter__" methods in classes like "BadAsyncIterator" usually return "self" at runtime. Consider using "typing_extensions.Self" in "BadAsyncIterator.__aiter__", e.g. "def __aiter__(self) -> Self: ..." # Y022 Use "collections.abc.AsyncIterator[T]" instead of "typing.AsyncIterator[T]" (PEP 585 syntax)
|
||||
149 147 |
|
||||
150 148 | class AsyncIteratorReturningAsyncIterable:
|
||||
151 149 | def __aiter__(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI062.py:5:25: PYI062 [*] Duplicate literal member `True`
|
||||
PYI062.py:5:25: PYI062 Duplicate literal member `True`
|
||||
|
|
||||
3 | import typing_extensions
|
||||
4 |
|
||||
@@ -10,19 +10,8 @@ PYI062.py:5:25: PYI062 [*] Duplicate literal member `True`
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | import typing as t
|
||||
3 3 | import typing_extensions
|
||||
4 4 |
|
||||
5 |-x: Literal[True, False, True, False] # PYI062 twice here
|
||||
5 |+x: Literal[True, False] # PYI062 twice here
|
||||
6 6 |
|
||||
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
8 8 |
|
||||
|
||||
PYI062.py:5:31: PYI062 [*] Duplicate literal member `False`
|
||||
PYI062.py:5:31: PYI062 Duplicate literal member `False`
|
||||
|
|
||||
3 | import typing_extensions
|
||||
4 |
|
||||
@@ -31,19 +20,8 @@ PYI062.py:5:31: PYI062 [*] Duplicate literal member `False`
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | import typing as t
|
||||
3 3 | import typing_extensions
|
||||
4 4 |
|
||||
5 |-x: Literal[True, False, True, False] # PYI062 twice here
|
||||
5 |+x: Literal[True, False] # PYI062 twice here
|
||||
6 6 |
|
||||
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
8 8 |
|
||||
|
||||
PYI062.py:7:45: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.py:7:45: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
5 | x: Literal[True, False, True, False] # PYI062 twice here
|
||||
6 |
|
||||
@@ -52,19 +30,8 @@ PYI062.py:7:45: PYI062 [*] Duplicate literal member `1`
|
||||
8 |
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 |
|
||||
5 5 | x: Literal[True, False, True, False] # PYI062 twice here
|
||||
6 6 |
|
||||
7 |-y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
7 |+y: Literal[1, print("hello"), 3, 4] # PYI062 on the last 1
|
||||
8 8 |
|
||||
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
10 10 |
|
||||
|
||||
PYI062.py:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}`
|
||||
PYI062.py:9:33: PYI062 Duplicate literal member `{1, 3, 5}`
|
||||
|
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
8 |
|
||||
@@ -73,19 +40,8 @@ PYI062.py:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}`
|
||||
10 |
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 |
|
||||
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1
|
||||
8 8 |
|
||||
9 |-z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
9 |+z: Literal[{1, 3, 5}, "foobar"] # PYI062 on the set literal
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
|
||||
PYI062.py:11:20: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.py:11:20: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
10 |
|
||||
@@ -94,19 +50,8 @@ PYI062.py:11:20: PYI062 [*] Duplicate literal member `1`
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 |
|
||||
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
10 10 |
|
||||
11 |-Literal[1, Literal[1]] # once
|
||||
11 |+Literal[1] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
||||
PYI062.py:12:23: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.py:12:23: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
@@ -114,19 +59,8 @@ PYI062.py:12:23: PYI062 [*] Duplicate literal member `1`
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 |-Literal[1, 2, Literal[1, 2]] # twice
|
||||
12 |+Literal[1, 2] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
||||
PYI062.py:12:26: PYI062 [*] Duplicate literal member `2`
|
||||
PYI062.py:12:26: PYI062 Duplicate literal member `2`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
@@ -134,19 +68,8 @@ PYI062.py:12:26: PYI062 [*] Duplicate literal member `2`
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 |-Literal[1, 2, Literal[1, 2]] # twice
|
||||
12 |+Literal[1, 2] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
||||
PYI062.py:13:20: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.py:13:20: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
@@ -155,19 +78,8 @@ PYI062.py:13:20: PYI062 [*] Duplicate literal member `1`
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 |-Literal[1, Literal[1], Literal[1]] # twice
|
||||
13 |+Literal[1] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
||||
PYI062.py:13:32: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.py:13:32: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
@@ -176,19 +88,8 @@ PYI062.py:13:32: PYI062 [*] Duplicate literal member `1`
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 |-Literal[1, Literal[1], Literal[1]] # twice
|
||||
13 |+Literal[1] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
||||
PYI062.py:14:32: PYI062 [*] Duplicate literal member `2`
|
||||
PYI062.py:14:32: PYI062 Duplicate literal member `2`
|
||||
|
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
@@ -197,19 +98,8 @@ PYI062.py:14:32: PYI062 [*] Duplicate literal member `2`
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 |-Literal[1, Literal[2], Literal[2]] # once
|
||||
14 |+Literal[1, 2] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
17 17 |
|
||||
|
||||
PYI062.py:15:37: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.py:15:37: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
@@ -217,19 +107,8 @@ PYI062.py:15:37: PYI062 [*] Duplicate literal member `1`
|
||||
| ^ PYI062
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 |-t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
15 |+t.Literal[1, 2] # once
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
17 17 |
|
||||
18 18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
||||
PYI062.py:16:30: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.py:16:30: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
@@ -238,19 +117,8 @@ PYI062.py:16:30: PYI062 [*] Duplicate literal member `1`
|
||||
17 |
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 |-typing_extensions.Literal[1, 1, 1] # twice
|
||||
16 |+typing_extensions.Literal[1] # twice
|
||||
17 17 |
|
||||
18 18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
|
||||
PYI062.py:16:33: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.py:16:33: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
@@ -259,19 +127,8 @@ PYI062.py:16:33: PYI062 [*] Duplicate literal member `1`
|
||||
17 |
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 |-typing_extensions.Literal[1, 1, 1] # twice
|
||||
16 |+typing_extensions.Literal[1] # twice
|
||||
17 17 |
|
||||
18 18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
|
||||
PYI062.py:19:46: PYI062 [*] Duplicate literal member `True`
|
||||
PYI062.py:19:46: PYI062 Duplicate literal member `True`
|
||||
|
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
@@ -279,13 +136,3 @@ PYI062.py:19:46: PYI062 [*] Duplicate literal member `True`
|
||||
20 |
|
||||
21 | n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
17 17 |
|
||||
18 18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 |-MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
19 |+MyType = Literal["foo", True, False, "bar"] # PYI062
|
||||
20 20 |
|
||||
21 21 | n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI062.pyi:5:25: PYI062 [*] Duplicate literal member `True`
|
||||
PYI062.pyi:5:25: PYI062 Duplicate literal member `True`
|
||||
|
|
||||
3 | import typing_extensions
|
||||
4 |
|
||||
@@ -10,19 +10,8 @@ PYI062.pyi:5:25: PYI062 [*] Duplicate literal member `True`
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | import typing as t
|
||||
3 3 | import typing_extensions
|
||||
4 4 |
|
||||
5 |-x: Literal[True, False, True, False] # PY062 twice here
|
||||
5 |+x: Literal[True, False] # PY062 twice here
|
||||
6 6 |
|
||||
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
8 8 |
|
||||
|
||||
PYI062.pyi:5:31: PYI062 [*] Duplicate literal member `False`
|
||||
PYI062.pyi:5:31: PYI062 Duplicate literal member `False`
|
||||
|
|
||||
3 | import typing_extensions
|
||||
4 |
|
||||
@@ -31,19 +20,8 @@ PYI062.pyi:5:31: PYI062 [*] Duplicate literal member `False`
|
||||
6 |
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | import typing as t
|
||||
3 3 | import typing_extensions
|
||||
4 4 |
|
||||
5 |-x: Literal[True, False, True, False] # PY062 twice here
|
||||
5 |+x: Literal[True, False] # PY062 twice here
|
||||
6 6 |
|
||||
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
8 8 |
|
||||
|
||||
PYI062.pyi:7:45: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.pyi:7:45: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
5 | x: Literal[True, False, True, False] # PY062 twice here
|
||||
6 |
|
||||
@@ -52,19 +30,8 @@ PYI062.pyi:7:45: PYI062 [*] Duplicate literal member `1`
|
||||
8 |
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 |
|
||||
5 5 | x: Literal[True, False, True, False] # PY062 twice here
|
||||
6 6 |
|
||||
7 |-y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
7 |+y: Literal[1, print("hello"), 3, 4] # PY062 on the last 1
|
||||
8 8 |
|
||||
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
10 10 |
|
||||
|
||||
PYI062.pyi:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}`
|
||||
PYI062.pyi:9:33: PYI062 Duplicate literal member `{1, 3, 5}`
|
||||
|
|
||||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
8 |
|
||||
@@ -73,19 +40,8 @@ PYI062.pyi:9:33: PYI062 [*] Duplicate literal member `{1, 3, 5}`
|
||||
10 |
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
6 6 |
|
||||
7 7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1
|
||||
8 8 |
|
||||
9 |-z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
9 |+z: Literal[{1, 3, 5}, "foobar"] # PY062 on the set literal
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
|
||||
PYI062.pyi:11:20: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.pyi:11:20: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
10 |
|
||||
@@ -94,19 +50,8 @@ PYI062.pyi:11:20: PYI062 [*] Duplicate literal member `1`
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 |
|
||||
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
10 10 |
|
||||
11 |-Literal[1, Literal[1]] # once
|
||||
11 |+Literal[1] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
||||
PYI062.pyi:12:23: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.pyi:12:23: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
@@ -114,19 +59,8 @@ PYI062.pyi:12:23: PYI062 [*] Duplicate literal member `1`
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 |-Literal[1, 2, Literal[1, 2]] # twice
|
||||
12 |+Literal[1, 2] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
||||
PYI062.pyi:12:26: PYI062 [*] Duplicate literal member `2`
|
||||
PYI062.pyi:12:26: PYI062 Duplicate literal member `2`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
@@ -134,19 +68,8 @@ PYI062.pyi:12:26: PYI062 [*] Duplicate literal member `2`
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
9 9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 |-Literal[1, 2, Literal[1, 2]] # twice
|
||||
12 |+Literal[1, 2] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
||||
PYI062.pyi:13:20: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.pyi:13:20: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
@@ -155,19 +78,8 @@ PYI062.pyi:13:20: PYI062 [*] Duplicate literal member `1`
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 |-Literal[1, Literal[1], Literal[1]] # twice
|
||||
13 |+Literal[1] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
||||
PYI062.pyi:13:32: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.pyi:13:32: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
11 | Literal[1, Literal[1]] # once
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
@@ -176,19 +88,8 @@ PYI062.pyi:13:32: PYI062 [*] Duplicate literal member `1`
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 |-Literal[1, Literal[1], Literal[1]] # twice
|
||||
13 |+Literal[1] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
||||
PYI062.pyi:14:32: PYI062 [*] Duplicate literal member `2`
|
||||
PYI062.pyi:14:32: PYI062 Duplicate literal member `2`
|
||||
|
|
||||
12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
@@ -197,19 +98,8 @@ PYI062.pyi:14:32: PYI062 [*] Duplicate literal member `2`
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
11 11 | Literal[1, Literal[1]] # once
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 |-Literal[1, Literal[2], Literal[2]] # once
|
||||
14 |+Literal[1, 2] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
17 17 |
|
||||
|
||||
PYI062.pyi:15:37: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.pyi:15:37: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
@@ -217,19 +107,8 @@ PYI062.pyi:15:37: PYI062 [*] Duplicate literal member `1`
|
||||
| ^ PYI062
|
||||
16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
12 12 | Literal[1, 2, Literal[1, 2]] # twice
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 |-t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
15 |+t.Literal[1, 2] # once
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
17 17 |
|
||||
18 18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
||||
PYI062.pyi:16:30: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.pyi:16:30: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
@@ -238,19 +117,8 @@ PYI062.pyi:16:30: PYI062 [*] Duplicate literal member `1`
|
||||
17 |
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 |-typing_extensions.Literal[1, 1, 1] # twice
|
||||
16 |+typing_extensions.Literal[1] # twice
|
||||
17 17 |
|
||||
18 18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
|
||||
PYI062.pyi:16:33: PYI062 [*] Duplicate literal member `1`
|
||||
PYI062.pyi:16:33: PYI062 Duplicate literal member `1`
|
||||
|
|
||||
14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
@@ -259,19 +127,8 @@ PYI062.pyi:16:33: PYI062 [*] Duplicate literal member `1`
|
||||
17 |
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
13 13 | Literal[1, Literal[1], Literal[1]] # twice
|
||||
14 14 | Literal[1, Literal[2], Literal[2]] # once
|
||||
15 15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once
|
||||
16 |-typing_extensions.Literal[1, 1, 1] # twice
|
||||
16 |+typing_extensions.Literal[1] # twice
|
||||
17 17 |
|
||||
18 18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
|
||||
PYI062.pyi:19:46: PYI062 [*] Duplicate literal member `True`
|
||||
PYI062.pyi:19:46: PYI062 Duplicate literal member `True`
|
||||
|
|
||||
18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
@@ -279,13 +136,3 @@ PYI062.pyi:19:46: PYI062 [*] Duplicate literal member `True`
|
||||
20 |
|
||||
21 | n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
|
|
||||
= help: Remove duplicates
|
||||
|
||||
ℹ Safe fix
|
||||
16 16 | typing_extensions.Literal[1, 1, 1] # twice
|
||||
17 17 |
|
||||
18 18 | # Ensure issue is only raised once, even on nested literals
|
||||
19 |-MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062
|
||||
19 |+MyType = Literal["foo", True, False, "bar"] # PYI062
|
||||
20 20 |
|
||||
21 21 | n: Literal["No", "duplicates", "here", 1, "1"]
|
||||
|
||||
@@ -590,7 +590,7 @@ impl AlwaysFixableViolation for PytestErroneousUseFixturesOnFixture {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PyPI: `pytest-asyncio`](https://pypi.org/project/pytest-asyncio/)
|
||||
/// - [`pytest-asyncio`](https://pypi.org/project/pytest-asyncio/)
|
||||
#[violation]
|
||||
pub struct PytestUnnecessaryAsyncioMarkOnFixture;
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ use ruff_text_size::Ranged;
|
||||
///
|
||||
/// ## References
|
||||
/// - [Python documentation: `unittest.mock.patch`](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch)
|
||||
/// - [PyPI: `pytest-mock`](https://pypi.org/project/pytest-mock/)
|
||||
/// - [`pytest-mock`](https://pypi.org/project/pytest-mock/)
|
||||
#[violation]
|
||||
pub struct PytestPatchWithLambda;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ use crate::fix;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
/// - [PEP 535](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
#[violation]
|
||||
pub struct EmptyTypeCheckingBlock;
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ use crate::rules::flake8_type_checking::imports::ImportBinding;
|
||||
/// - `lint.flake8-type-checking.quote-annotations`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
/// - [PEP 535](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
#[violation]
|
||||
pub struct RuntimeImportInTypeCheckingBlock {
|
||||
qualified_name: String,
|
||||
|
||||
@@ -37,8 +37,8 @@ use crate::checkers::ast::Checker;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563 - Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/)
|
||||
/// - [PEP 604 – Allow writing union types as `X | Y`](https://peps.python.org/pep-0604/)
|
||||
/// - [PEP 535](https://peps.python.org/pep-0563/)
|
||||
/// - [PEP 604](https://peps.python.org/pep-0604/)
|
||||
///
|
||||
/// [PEP 604]: https://peps.python.org/pep-0604/
|
||||
#[violation]
|
||||
|
||||
@@ -71,7 +71,7 @@ use crate::rules::isort::{categorize, ImportSection, ImportType};
|
||||
/// - `lint.typing-modules`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
/// - [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
#[violation]
|
||||
pub struct TypingOnlyFirstPartyImport {
|
||||
qualified_name: String,
|
||||
@@ -146,7 +146,7 @@ impl Violation for TypingOnlyFirstPartyImport {
|
||||
/// - `lint.typing-modules`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
/// - [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
#[violation]
|
||||
pub struct TypingOnlyThirdPartyImport {
|
||||
qualified_name: String,
|
||||
@@ -221,7 +221,7 @@ impl Violation for TypingOnlyThirdPartyImport {
|
||||
/// - `lint.typing-modules`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563: Runtime annotation resolution and `TYPE_CHECKING`](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
/// - [PEP 536](https://peps.python.org/pep-0563/#runtime-annotation-resolution-and-type-checking)
|
||||
#[violation]
|
||||
pub struct TypingOnlyStandardLibraryImport {
|
||||
qualified_name: String,
|
||||
|
||||
@@ -19,10 +19,6 @@ use crate::registry::Rule;
|
||||
/// An argument that is defined but not used is likely a mistake, and should
|
||||
/// be removed to avoid confusion.
|
||||
///
|
||||
/// If a variable is intentionally defined-but-not-used, it should be
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`lint.dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// def foo(bar, baz):
|
||||
@@ -34,9 +30,6 @@ use crate::registry::Rule;
|
||||
/// def foo(bar):
|
||||
/// return bar * 2
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.dummy-variable-rgx`
|
||||
#[violation]
|
||||
pub struct UnusedFunctionArgument {
|
||||
name: String,
|
||||
@@ -57,10 +50,6 @@ impl Violation for UnusedFunctionArgument {
|
||||
/// An argument that is defined but not used is likely a mistake, and should
|
||||
/// be removed to avoid confusion.
|
||||
///
|
||||
/// If a variable is intentionally defined-but-not-used, it should be
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`lint.dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Class:
|
||||
@@ -74,9 +63,6 @@ impl Violation for UnusedFunctionArgument {
|
||||
/// def foo(self, arg1):
|
||||
/// print(arg1)
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.dummy-variable-rgx`
|
||||
#[violation]
|
||||
pub struct UnusedMethodArgument {
|
||||
name: String,
|
||||
@@ -97,10 +83,6 @@ impl Violation for UnusedMethodArgument {
|
||||
/// An argument that is defined but not used is likely a mistake, and should
|
||||
/// be removed to avoid confusion.
|
||||
///
|
||||
/// If a variable is intentionally defined-but-not-used, it should be
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`lint.dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Class:
|
||||
@@ -116,9 +98,6 @@ impl Violation for UnusedMethodArgument {
|
||||
/// def foo(cls, arg1):
|
||||
/// print(arg1)
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.dummy-variable-rgx`
|
||||
#[violation]
|
||||
pub struct UnusedClassMethodArgument {
|
||||
name: String,
|
||||
@@ -139,10 +118,6 @@ impl Violation for UnusedClassMethodArgument {
|
||||
/// An argument that is defined but not used is likely a mistake, and should
|
||||
/// be removed to avoid confusion.
|
||||
///
|
||||
/// If a variable is intentionally defined-but-not-used, it should be
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`lint.dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// class Class:
|
||||
@@ -158,9 +133,6 @@ impl Violation for UnusedClassMethodArgument {
|
||||
/// def foo(arg1):
|
||||
/// print(arg1)
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.dummy-variable-rgx`
|
||||
#[violation]
|
||||
pub struct UnusedStaticMethodArgument {
|
||||
name: String,
|
||||
@@ -182,10 +154,6 @@ impl Violation for UnusedStaticMethodArgument {
|
||||
/// An argument that is defined but not used is likely a mistake, and should
|
||||
/// be removed to avoid confusion.
|
||||
///
|
||||
/// If a variable is intentionally defined-but-not-used, it should be
|
||||
/// prefixed with an underscore, or some other value that adheres to the
|
||||
/// [`lint.dummy-variable-rgx`] pattern.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// my_list = [1, 2, 3, 4, 5]
|
||||
@@ -197,9 +165,6 @@ impl Violation for UnusedStaticMethodArgument {
|
||||
/// my_list = [1, 2, 3, 4, 5]
|
||||
/// squares = map(lambda x: x**2, my_list)
|
||||
/// ```
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.dummy-variable-rgx`
|
||||
#[violation]
|
||||
pub struct UnusedLambdaArgument {
|
||||
name: String,
|
||||
|
||||
@@ -32,7 +32,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getatime`](https://docs.python.org/3/library/os.path.html#os.path.getatime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
|
||||
@@ -32,7 +32,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getctime`](https://docs.python.org/3/library/os.path.html#os.path.getctime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
|
||||
@@ -32,7 +32,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getmtime`](https://docs.python.org/3/library/os.path.html#os.path.getmtime)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
|
||||
@@ -32,7 +32,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.stat`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.stat)
|
||||
/// - [Python documentation: `os.path.getsize`](https://docs.python.org/3/library/os.path.html#os.path.getsize)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
|
||||
@@ -30,7 +30,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.resolve`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.resolve)
|
||||
/// - [Python documentation: `os.path.abspath`](https://docs.python.org/3/library/os.path.html#os.path.abspath)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -73,7 +73,7 @@ impl Violation for OsPathAbspath {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.chmod`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.chmod)
|
||||
/// - [Python documentation: `os.chmod`](https://docs.python.org/3/library/os.html#os.chmod)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -116,7 +116,7 @@ impl Violation for OsChmod {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
|
||||
/// - [Python documentation: `os.makedirs`](https://docs.python.org/3/library/os.html#os.makedirs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -159,7 +159,7 @@ impl Violation for OsMakedirs {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.mkdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir)
|
||||
/// - [Python documentation: `os.mkdir`](https://docs.python.org/3/library/os.html#os.mkdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -202,7 +202,7 @@ impl Violation for OsMkdir {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.rename`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rename)
|
||||
/// - [Python documentation: `os.rename`](https://docs.python.org/3/library/os.html#os.rename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -245,7 +245,7 @@ impl Violation for OsRename {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.replace`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.replace)
|
||||
/// - [Python documentation: `os.replace`](https://docs.python.org/3/library/os.html#os.replace)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -288,7 +288,7 @@ impl Violation for OsReplace {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.rmdir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.rmdir)
|
||||
/// - [Python documentation: `os.rmdir`](https://docs.python.org/3/library/os.html#os.rmdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -331,7 +331,7 @@ impl Violation for OsRmdir {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.remove`](https://docs.python.org/3/library/os.html#os.remove)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -374,7 +374,7 @@ impl Violation for OsRemove {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.unlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.unlink)
|
||||
/// - [Python documentation: `os.unlink`](https://docs.python.org/3/library/os.html#os.unlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -418,7 +418,7 @@ impl Violation for OsUnlink {
|
||||
/// - [Python documentation: `Path.cwd`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.cwd)
|
||||
/// - [Python documentation: `os.getcwd`](https://docs.python.org/3/library/os.html#os.getcwd)
|
||||
/// - [Python documentation: `os.getcwdb`](https://docs.python.org/3/library/os.html#os.getcwdb)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -461,7 +461,7 @@ impl Violation for OsGetcwd {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.exists`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.exists)
|
||||
/// - [Python documentation: `os.path.exists`](https://docs.python.org/3/library/os.path.html#os.path.exists)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -504,7 +504,7 @@ impl Violation for OsPathExists {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.expanduser`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.expanduser)
|
||||
/// - [Python documentation: `os.path.expanduser`](https://docs.python.org/3/library/os.path.html#os.path.expanduser)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -547,7 +547,7 @@ impl Violation for OsPathExpanduser {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_dir`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_dir)
|
||||
/// - [Python documentation: `os.path.isdir`](https://docs.python.org/3/library/os.path.html#os.path.isdir)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -590,7 +590,7 @@ impl Violation for OsPathIsdir {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_file`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_file)
|
||||
/// - [Python documentation: `os.path.isfile`](https://docs.python.org/3/library/os.path.html#os.path.isfile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -633,7 +633,7 @@ impl Violation for OsPathIsfile {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.is_symlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.is_symlink)
|
||||
/// - [Python documentation: `os.path.islink`](https://docs.python.org/3/library/os.path.html#os.path.islink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -676,7 +676,7 @@ impl Violation for OsPathIslink {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.readlink`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.readline)
|
||||
/// - [Python documentation: `os.readlink`](https://docs.python.org/3/library/os.html#os.readlink)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -728,7 +728,7 @@ impl Violation for OsReadlink {
|
||||
/// - [Python documentation: `Path.group`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.group)
|
||||
/// - [Python documentation: `Path.owner`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.owner)
|
||||
/// - [Python documentation: `os.stat`](https://docs.python.org/3/library/os.html#os.stat)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -774,7 +774,7 @@ impl Violation for OsStat {
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.is_absolute`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.is_absolute)
|
||||
/// - [Python documentation: `os.path.isabs`](https://docs.python.org/3/library/os.path.html#os.path.isabs)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -817,7 +817,7 @@ impl Violation for OsPathIsabs {
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.joinpath`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.joinpath)
|
||||
/// - [Python documentation: `os.path.join`](https://docs.python.org/3/library/os.path.html#os.path.join)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -877,7 +877,7 @@ pub(crate) enum Joiner {
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.name`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.name)
|
||||
/// - [Python documentation: `os.path.basename`](https://docs.python.org/3/library/os.path.html#os.path.basename)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -920,7 +920,7 @@ impl Violation for OsPathBasename {
|
||||
/// ## References
|
||||
/// - [Python documentation: `PurePath.parent`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.parent)
|
||||
/// - [Python documentation: `os.path.dirname`](https://docs.python.org/3/library/os.path.html#os.path.dirname)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -963,7 +963,7 @@ impl Violation for OsPathDirname {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.samefile`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.samefile)
|
||||
/// - [Python documentation: `os.path.samefile`](https://docs.python.org/3/library/os.path.html#os.path.samefile)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -1015,7 +1015,7 @@ impl Violation for OsPathSamefile {
|
||||
/// - [Python documentation: `Path.suffix`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffix)
|
||||
/// - [Python documentation: `Path.suffixes`](https://docs.python.org/3/library/pathlib.html#pathlib.PurePath.suffixes)
|
||||
/// - [Python documentation: `os.path.splitext`](https://docs.python.org/3/library/os.path.html#os.path.splitext)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
@@ -1055,7 +1055,7 @@ impl Violation for OsPathSplitext {
|
||||
/// ## References
|
||||
/// - [Python documentation: `Path.open`](https://docs.python.org/3/library/pathlib.html#pathlib.Path.open)
|
||||
/// - [Python documentation: `open`](https://docs.python.org/3/library/functions.html#open)
|
||||
/// - [PEP 428 – The pathlib module – object-oriented filesystem paths](https://peps.python.org/pep-0428/)
|
||||
/// - [PEP 428](https://peps.python.org/pep-0428/)
|
||||
/// - [Correspondence between `os` and `pathlib`](https://docs.python.org/3/library/pathlib.html#correspondence-to-tools-in-the-os-module)
|
||||
/// - [Why you should be using pathlib](https://treyhunner.com/2018/12/why-you-should-be-using-pathlib/)
|
||||
/// - [No really, pathlib is great](https://treyhunner.com/2019/01/no-really-pathlib-is-great/)
|
||||
|
||||
@@ -33,7 +33,7 @@ use crate::Locator;
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [_Why You Should Probably Never Use pandas `inplace=True`_](https://towardsdatascience.com/why-you-should-probably-never-use-pandas-inplace-true-9f9f211849e4)
|
||||
/// - [_Why You Should Probably Never Use pandas inplace=True_](https://towardsdatascience.com/why-you-should-probably-never-use-pandas-inplace-true-9f9f211849e4)
|
||||
#[violation]
|
||||
pub struct PandasUseOfInplaceArgument;
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ use crate::rules::pep8_naming::helpers;
|
||||
/// from example import MyClassName
|
||||
/// ```
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/
|
||||
///
|
||||
/// ## Options
|
||||
/// - `lint.flake8-import-conventions.aliases`
|
||||
///
|
||||
/// [PEP 8]: https://peps.python.org/pep-0008/
|
||||
#[violation]
|
||||
pub struct CamelcaseImportedAsAcronym {
|
||||
name: String,
|
||||
|
||||
@@ -61,7 +61,7 @@ const BLANK_LINES_NESTED_LEVEL: u32 = 1;
|
||||
/// them. That's why this rule is not enabled in typing stub files.
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E301.html)
|
||||
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
|
||||
#[violation]
|
||||
@@ -114,7 +114,7 @@ impl AlwaysFixableViolation for BlankLineBetweenMethods {
|
||||
/// - `lint.isort.lines-after-imports`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E302.html)
|
||||
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
|
||||
#[violation]
|
||||
@@ -181,7 +181,7 @@ impl AlwaysFixableViolation for BlankLinesTopLevel {
|
||||
/// - `lint.isort.lines-between-types`
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E303.html)
|
||||
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
|
||||
#[violation]
|
||||
@@ -228,7 +228,7 @@ impl AlwaysFixableViolation for TooManyBlankLines {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E304.html)
|
||||
#[violation]
|
||||
pub struct BlankLineAfterDecorator {
|
||||
@@ -278,7 +278,7 @@ impl AlwaysFixableViolation for BlankLineAfterDecorator {
|
||||
/// them. That's why this rule is not enabled in typing stub files.
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E305.html)
|
||||
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
|
||||
#[violation]
|
||||
@@ -332,7 +332,7 @@ impl AlwaysFixableViolation for BlankLinesAfterFunctionOrClass {
|
||||
/// them. That's why this rule is not enabled in typing stub files.
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 8: Blank Lines](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [PEP 8](https://peps.python.org/pep-0008/#blank-lines)
|
||||
/// - [Flake 8 rule](https://www.flake8rules.com/rules/E306.html)
|
||||
/// - [Typing Style Guide](https://typing.readthedocs.io/en/latest/source/stubs.html#blank-lines)
|
||||
#[violation]
|
||||
|
||||
@@ -52,7 +52,7 @@ use crate::Locator;
|
||||
/// See [#10885](https://github.com/astral-sh/ruff/issues/10885) for more.
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 498 – Literal String Interpolation](https://peps.python.org/pep-0498/)
|
||||
/// - [PEP 498](https://www.python.org/dev/peps/pep-0498/)
|
||||
#[violation]
|
||||
pub struct FStringMissingPlaceholders;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 563 – Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/)
|
||||
/// - [PEP 563](https://www.python.org/dev/peps/pep-0563/)
|
||||
#[violation]
|
||||
pub struct ForwardAnnotationSyntaxError {
|
||||
pub body: String,
|
||||
|
||||
@@ -8,7 +8,7 @@ use ruff_text_size::Ranged;
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `if` statements that use non-empty tuples as test conditions.
|
||||
/// Checks for `if statements that use non-empty tuples as test conditions.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Non-empty tuples are always `True`, so an `if` statement with a non-empty
|
||||
|
||||
@@ -13,9 +13,6 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
/// In Python 3, no more than 1 << 8 assignments are allowed before a starred
|
||||
/// expression, and no more than 1 << 24 expressions are allowed after a starred
|
||||
/// expression.
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 3132 – Extended Iterable Unpacking](https://peps.python.org/pep-3132/)
|
||||
#[violation]
|
||||
pub struct ExpressionsInStarAssignment;
|
||||
|
||||
@@ -41,7 +38,7 @@ impl Violation for ExpressionsInStarAssignment {
|
||||
/// ```
|
||||
///
|
||||
/// ## References
|
||||
/// - [PEP 3132 – Extended Iterable Unpacking](https://peps.python.org/pep-3132/)
|
||||
/// - [PEP 3132](https://peps.python.org/pep-3132/)
|
||||
#[violation]
|
||||
pub struct MultipleStarredExpressions;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user