Compare commits

..

1 Commits

Author SHA1 Message Date
Micha Reiser
0c27a95426 Prefer breaking return-type over parameters 2024-11-07 08:38:05 +01:00
122 changed files with 1226 additions and 3360 deletions

View File

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

View File

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

7
Cargo.lock generated
View File

@@ -2173,6 +2173,7 @@ dependencies = [
"regex",
"ruff_db",
"ruff_index",
"ruff_python_parser",
"ruff_python_trivia",
"ruff_source_file",
"ruff_text_size",
@@ -2317,7 +2318,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.7.3"
version = "0.7.2"
dependencies = [
"anyhow",
"argfile",
@@ -2534,7 +2535,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.7.3"
version = "0.7.2"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2849,7 +2850,7 @@ dependencies = [
[[package]]
name = "ruff_wasm"
version = "0.7.3"
version = "0.7.2"
dependencies = [
"console_error_panic_hook",
"console_log",

View File

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

View File

@@ -5,6 +5,8 @@ use anyhow::{anyhow, Context};
use clap::Parser;
use colored::Colorize;
use crossbeam::channel as crossbeam_channel;
use salsa::plumbing::ZalsaDatabase;
use red_knot_python_semantic::SitePackages;
use red_knot_server::run_server;
use red_knot_workspace::db::RootDatabase;
@@ -12,9 +14,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};
@@ -318,9 +318,8 @@ impl MainLoop {
} => {
let has_diagnostics = !result.is_empty();
if check_revision == revision {
#[allow(clippy::print_stdout)]
for diagnostic in result {
println!("{}", diagnostic.display(db));
tracing::error!("{}", diagnostic);
}
} else {
tracing::debug!(
@@ -379,10 +378,7 @@ impl MainLoopCancellationToken {
#[derive(Debug)]
enum MainLoopMessage {
CheckWorkspace,
CheckCompleted {
result: Vec<Box<dyn Diagnostic>>,
revision: u64,
},
CheckCompleted { result: Vec<String>, revision: u64 },
ApplyChanges(Vec<watch::ChangeEvent>),
Exit,
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -65,7 +65,7 @@ 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"
# error: [conflicting-metaclass] "The metaclass of a derived class (`C`) must be a subclass of the metaclasses of all its bases, but `M1` and `M2` have no subclass relationship"
class C(A, B): ...
reveal_type(C.__class__) # revealed: Unknown
@@ -82,7 +82,7 @@ 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"
# error: [conflicting-metaclass] "The metaclass of a derived class (`B`) must be a subclass of the metaclasses of all its bases, but `M2` and `M1` have no subclass relationship"
class B(A, metaclass=M2): ...
reveal_type(B.__class__) # revealed: Unknown
@@ -126,7 +126,7 @@ 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"
# error: [conflicting-metaclass] "The metaclass of a derived class (`D`) must be a subclass of the metaclasses of all its bases, but `M1` and `M2` have no subclass relationship"
class D(A, B, C): ...
reveal_type(D.__class__) # revealed: Unknown

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,13 @@
use crate::types::{ClassLiteralType, Type};
use crate::Db;
use ruff_db::diagnostic::{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;
use crate::types::{ClassLiteralType, Type};
use crate::Db;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct TypeCheckDiagnostic {
// TODO: Don't use string keys for rules
@@ -32,28 +31,6 @@ impl TypeCheckDiagnostic {
}
}
impl Diagnostic for TypeCheckDiagnostic {
fn rule(&self) -> &str {
TypeCheckDiagnostic::rule(self)
}
fn message(&self) -> Cow<str> {
TypeCheckDiagnostic::message(self).into()
}
fn file(&self) -> File {
TypeCheckDiagnostic::file(self)
}
fn range(&self) -> Option<TextRange> {
Some(Ranged::range(self))
}
fn severity(&self) -> Severity {
Severity::Error
}
}
impl Ranged for TypeCheckDiagnostic {
fn range(&self) -> TextRange {
self.range
@@ -164,23 +141,6 @@ impl<'db> TypeCheckDiagnosticsBuilder<'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,

View File

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

View File

@@ -57,9 +57,9 @@ use crate::types::unpacker::{UnpackResult, Unpacker};
use crate::types::{
bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, typing_extensions_symbol,
Boundness, BytesLiteralType, Class, ClassLiteralType, FunctionType, InstanceType,
IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction,
KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, StringLiteralType,
Symbol, Truthiness, TupleType, Type, TypeArrayDisplay, UnionBuilder, UnionType,
IterationOutcome, KnownClass, KnownFunction, KnownInstance, MetaclassErrorKind,
SliceLiteralType, StringLiteralType, Symbol, Truthiness, TupleType, Type, TypeArrayDisplay,
UnionBuilder, UnionType,
};
use crate::unpack::Unpack;
use crate::util::subscript::{PyIndex, PySlice};
@@ -266,13 +266,6 @@ impl<'db> TypeInference<'db> {
}
}
/// Whether the intersection type is on the left or right side of the comparison.
#[derive(Debug, Clone, Copy)]
enum IntersectionOn {
Left,
Right,
}
/// Builder to infer all types in a region.
///
/// A builder is used by creating it with [`new()`](TypeInferenceBuilder::new), and then calling
@@ -520,50 +513,18 @@ impl<'db> TypeInferenceBuilder<'db> {
if let Err(metaclass_error) = class.try_metaclass(self.db) {
match metaclass_error.reason() {
MetaclassErrorKind::Conflict {
candidate1:
MetaclassCandidate {
metaclass: metaclass1,
explicit_metaclass_of: class1,
},
candidate2:
MetaclassCandidate {
metaclass: metaclass2,
explicit_metaclass_of: class2,
},
candidate1_is_base_class,
} => {
let node = class.node(self.db).into();
if *candidate1_is_base_class {
self.diagnostics.add(
node,
"conflicting-metaclass",
format_args!(
"The metaclass of a derived class (`{class}`) must be a subclass of the metaclasses of all its bases, \
but `{metaclass1}` (metaclass of base class `{base1}`) and `{metaclass2}` (metaclass of base class `{base2}`) \
have no subclass relationship",
class = class.name(self.db),
metaclass1 = metaclass1.name(self.db),
base1 = class1.name(self.db),
metaclass2 = metaclass2.name(self.db),
base2 = class2.name(self.db),
)
);
} else {
self.diagnostics.add(
node,
"conflicting-metaclass",
format_args!(
"The metaclass of a derived class (`{class}`) must be a subclass of the metaclasses of all its bases, \
but `{metaclass_of_class}` (metaclass of `{class}`) and `{metaclass_of_base}` (metaclass of base class `{base}`) \
have no subclass relationship",
class = class.name(self.db),
metaclass_of_class = metaclass1.name(self.db),
metaclass_of_base = metaclass2.name(self.db),
base = class2.name(self.db),
)
);
}
}
metaclass1,
metaclass2
} => self.diagnostics.add(
class.node(self.db).into(),
"conflicting-metaclass",
format_args!(
"The metaclass of a derived class (`{}`) must be a subclass of the metaclasses of all its bases, but `{}` and `{}` have no subclass relationship",
class.name(self.db),
metaclass1.name(self.db),
metaclass2.name(self.db),
),
),
MetaclassErrorKind::CyclicDefinition => {
// Cyclic class definition diagnostic will already have been emitted above
// in MRO calculation.
@@ -666,11 +627,10 @@ impl<'db> TypeInferenceBuilder<'db> {
fn check_division_by_zero(&mut self, expr: &ast::ExprBinOp, left: Type<'db>) {
match left {
Type::BooleanLiteral(_) | Type::IntLiteral(_) => {}
Type::Instance(InstanceType { class })
if matches!(
class.known(self.db),
Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool)
) => {}
Type::Instance(InstanceType { class, .. })
if [KnownClass::Float, KnownClass::Int, KnownClass::Bool]
.iter()
.any(|&k| class.is_known(self.db, k)) => {}
_ => return,
};
@@ -901,7 +861,15 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
let function_kind = KnownFunction::from_definition(self.db, definition, name);
let function_kind = match &**name {
"reveal_type" if definition.is_typing_definition(self.db) => {
Some(KnownFunction::RevealType)
}
"isinstance" if definition.is_builtin_definition(self.db) => {
Some(KnownFunction::IsInstance)
}
_ => None,
};
let body_scope = self
.index
@@ -1329,15 +1297,13 @@ impl<'db> TypeInferenceBuilder<'db> {
// anything else is invalid and should lead to a diagnostic being reported --Alex
match node_ty {
Type::Any | Type::Unknown => node_ty,
Type::ClassLiteral(ClassLiteralType { class }) => {
Type::Instance(InstanceType { class })
}
Type::ClassLiteral(ClassLiteralType { class }) => Type::anonymous_instance(class),
Type::Tuple(tuple) => UnionType::from_elements(
self.db,
tuple.elements(self.db).iter().map(|ty| {
ty.into_class_literal()
.map_or(Type::Todo, |ClassLiteralType { class }| {
Type::Instance(InstanceType { class })
Type::anonymous_instance(class)
})
}),
),
@@ -1537,16 +1503,14 @@ impl<'db> TypeInferenceBuilder<'db> {
// If the declared variable is annotated with _SpecialForm class then we treat it differently
// by assigning the known field to the instance.
if let Type::Instance(InstanceType { class }) = annotation_ty {
if let Type::Instance(InstanceType { class, .. }) = annotation_ty {
if class.is_known(self.db, KnownClass::SpecialForm) {
if let Some(name_expr) = target.as_name_expr() {
if let Some(known_instance) = file_to_module(self.db, self.file)
let maybe_known_instance = file_to_module(self.db, self.file)
.as_ref()
.and_then(|module| {
KnownInstanceType::try_from_module_and_symbol(module, &name_expr.id)
})
{
annotation_ty = Type::KnownInstance(known_instance);
.and_then(|module| KnownInstance::maybe_from_module(module, &name_expr.id));
if let Some(known_instance) = maybe_known_instance {
annotation_ty = Type::Instance(InstanceType::known(class, known_instance));
}
}
}
@@ -1591,7 +1555,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_augmented_op(assignment, target_type, value_type)
})
}
Type::Instance(InstanceType { class }) => {
Type::Instance(InstanceType { class, .. }) => {
if let Symbol::Type(class_member, boundness) =
class.class_member(self.db, op.in_place_dunder())
{
@@ -2753,7 +2717,7 @@ impl<'db> TypeInferenceBuilder<'db> {
(_, Type::Unknown) => Type::Unknown,
(
op @ (UnaryOp::UAdd | UnaryOp::USub | UnaryOp::Invert),
Type::Instance(InstanceType { class }),
Type::Instance(InstanceType { class, .. }),
) => {
let unary_dunder_method = match op {
UnaryOp::Invert => "__invert__",
@@ -3000,26 +2964,37 @@ impl<'db> TypeInferenceBuilder<'db> {
op,
),
(left_ty @ Type::Instance(left), right_ty @ Type::Instance(right), op) => {
if left != right && right.is_instance_of(self.db, left.class) {
(
Type::Instance(InstanceType {
class: left_class, ..
}),
Type::Instance(InstanceType {
class: right_class, ..
}),
op,
) => {
if left_class != right_class && right_class.is_subclass_of(self.db, left_class) {
let reflected_dunder = op.reflected_dunder();
let rhs_reflected = right.class.class_member(self.db, reflected_dunder);
let rhs_reflected = right_class.class_member(self.db, reflected_dunder);
if !rhs_reflected.is_unbound()
&& rhs_reflected != left.class.class_member(self.db, reflected_dunder)
&& rhs_reflected != left_class.class_member(self.db, reflected_dunder)
{
return right_ty
.call_dunder(self.db, reflected_dunder, &[right_ty, left_ty])
return rhs_reflected
.unwrap_or(Type::Never)
.call(self.db, &[right_ty, left_ty])
.return_ty(self.db)
.or_else(|| {
left_ty
.call_dunder(self.db, op.dunder(), &[left_ty, right_ty])
left_class
.class_member(self.db, op.dunder())
.unwrap_or(Type::Never)
.call(self.db, &[left_ty, right_ty])
.return_ty(self.db)
});
}
}
let call_on_left_instance = if let Symbol::Type(class_member, _) =
left.class.class_member(self.db, op.dunder())
left_class.class_member(self.db, op.dunder())
{
class_member
.call(self.db, &[left_ty, right_ty])
@@ -3029,11 +3004,11 @@ impl<'db> TypeInferenceBuilder<'db> {
};
call_on_left_instance.or_else(|| {
if left == right {
if left_class == right_class {
None
} else {
if let Symbol::Type(class_member, _) =
right.class.class_member(self.db, op.reflected_dunder())
right_class.class_member(self.db, op.reflected_dunder())
{
class_member
.call(self.db, &[right_ty, left_ty])
@@ -3129,7 +3104,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// https://docs.python.org/3/reference/expressions.html#comparisons
// > Formally, if `a, b, c, …, y, z` are expressions and `op1, op2, …, opN` are comparison
// > operators, then `a op1 b op2 c ... y opN z` is equivalent to `a op1 b and b op2 c and
// > operators, then `a op1 b op2 c ... y opN z` is equivalent to a `op1 b and b op2 c and
// ... > y opN z`, except that each expression is evaluated at most once.
//
// As some operators (==, !=, <, <=, >, >=) *can* return an arbitrary type, the logic below
@@ -3183,101 +3158,6 @@ impl<'db> TypeInferenceBuilder<'db> {
)
}
fn infer_binary_intersection_type_comparison(
&mut self,
intersection: IntersectionType<'db>,
op: ast::CmpOp,
other: Type<'db>,
intersection_on: IntersectionOn,
) -> Result<Type<'db>, CompareUnsupportedError<'db>> {
// If a comparison yields a definitive true/false answer on a (positive) part
// of an intersection type, it will also yield a definitive answer on the full
// intersection type, which is even more specific.
for pos in intersection.positive(self.db) {
let result = match intersection_on {
IntersectionOn::Left => self.infer_binary_type_comparison(*pos, op, other)?,
IntersectionOn::Right => self.infer_binary_type_comparison(other, op, *pos)?,
};
if let Type::BooleanLiteral(b) = result {
return Ok(Type::BooleanLiteral(b));
}
}
// For negative contributions to the intersection type, there are only a few
// special cases that allow us to narrow down the result type of the comparison.
for neg in intersection.negative(self.db) {
let result = match intersection_on {
IntersectionOn::Left => self.infer_binary_type_comparison(*neg, op, other).ok(),
IntersectionOn::Right => self.infer_binary_type_comparison(other, op, *neg).ok(),
};
match (op, result) {
(ast::CmpOp::Eq, Some(Type::BooleanLiteral(true))) => {
return Ok(Type::BooleanLiteral(false));
}
(ast::CmpOp::NotEq, Some(Type::BooleanLiteral(false))) => {
return Ok(Type::BooleanLiteral(true));
}
(ast::CmpOp::Is, Some(Type::BooleanLiteral(true))) => {
return Ok(Type::BooleanLiteral(false));
}
(ast::CmpOp::IsNot, Some(Type::BooleanLiteral(false))) => {
return Ok(Type::BooleanLiteral(true));
}
_ => {}
}
}
// If none of the simplifications above apply, we still need to return *some*
// result type for the comparison 'T_inter `op` T_other' (or reversed), where
//
// T_inter = P1 & P2 & ... & Pn & ~N1 & ~N2 & ... & ~Nm
//
// is the intersection type. If f(T) is the function that computes the result
// type of a `op`-comparison with `T_other`, we are interested in f(T_inter).
// Since we can't compute it exactly, we return the following approximation:
//
// f(T_inter) = f(P1) & f(P2) & ... & f(Pn)
//
// The reason for this is the following: In general, for any function 'f', the
// set f(A) & f(B) is *larger than or equal to* the set f(A & B). This means
// that we will return a type that is possibly wider than it could be, but
// never wrong.
//
// However, we do have to leave out the negative contributions. If we were to
// add a contribution like ~f(N1), we would potentially infer result types
// that are too narrow.
//
// As an example for this, consider the intersection type `int & ~Literal[1]`.
// If 'f' would be the `==`-comparison with 2, we obviously can't tell if that
// answer would be true or false, so we need to return `bool`. And indeed, we
// we have (glossing over notational details):
//
// f(int & ~1)
// = f({..., -1, 0, 2, 3, ...})
// = {..., False, False, True, False, ...}
// = bool
//
// On the other hand, if we were to compute
//
// f(int) & ~f(1)
// = bool & ~False
// = True
//
// we would get a result type `Literal[True]` which is too narrow.
//
let mut builder = IntersectionBuilder::new(self.db);
for pos in intersection.positive(self.db) {
let result = match intersection_on {
IntersectionOn::Left => self.infer_binary_type_comparison(*pos, op, other)?,
IntersectionOn::Right => self.infer_binary_type_comparison(other, op, *pos)?,
};
builder = builder.add_positive(result);
}
Ok(builder.build())
}
/// Infers the type of a binary comparison (e.g. 'left == right'). See
/// `infer_compare_expression` for the higher level logic dealing with multi-comparison
/// expressions.
@@ -3310,21 +3190,6 @@ impl<'db> TypeInferenceBuilder<'db> {
Ok(builder.build())
}
(Type::Intersection(intersection), right) => self
.infer_binary_intersection_type_comparison(
intersection,
op,
right,
IntersectionOn::Left,
),
(left, Type::Intersection(intersection)) => self
.infer_binary_intersection_type_comparison(
intersection,
op,
left,
IntersectionOn::Right,
),
(Type::IntLiteral(n), Type::IntLiteral(m)) => match op {
ast::CmpOp::Eq => Ok(Type::BooleanLiteral(n == m)),
ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(n != m)),
@@ -3884,7 +3749,7 @@ impl<'db> TypeInferenceBuilder<'db> {
Err(_) => SliceArg::Unsupported,
},
Some(Type::BooleanLiteral(b)) => SliceArg::Arg(Some(i32::from(b))),
Some(Type::Instance(InstanceType { class }))
Some(Type::Instance(InstanceType { class, .. }))
if class.is_known(self.db, KnownClass::NoneType) =>
{
SliceArg::Arg(None)
@@ -4017,15 +3882,15 @@ impl<'db> TypeInferenceBuilder<'db> {
let value_ty = self.infer_expression(value);
match value_ty {
Type::ClassLiteral(class_literal_ty) => {
match class_literal_ty.class.known(self.db) {
Some(KnownClass::Tuple) => self.infer_tuple_type_expression(slice),
Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice),
_ => self.infer_subscript_type_expression(subscript, value_ty),
}
}
_ => self.infer_subscript_type_expression(subscript, value_ty),
if value_ty
.into_class_literal()
.is_some_and(|ClassLiteralType { class }| {
class.is_known(self.db, KnownClass::Tuple)
})
{
self.infer_tuple_type_expression(slice)
} else {
self.infer_subscript_type_expression(subscript, value_ty)
}
}
@@ -4198,25 +4063,6 @@ impl<'db> TypeInferenceBuilder<'db> {
}
}
/// Given the slice of a `type[]` annotation, return the type that the annotation represents
fn infer_subclass_of_type_expression(&mut self, slice: &ast::Expr) -> Type<'db> {
match slice {
ast::Expr::Name(name) => {
let name_ty = self.infer_name_expression(name);
if let Some(class_literal) = name_ty.into_class_literal() {
Type::SubclassOf(class_literal.to_subclass_of_type())
} else {
Type::Todo
}
}
// TODO: attributes, unions, subscripts, etc.
_ => {
self.infer_type_expression(slice);
Type::Todo
}
}
}
fn infer_subscript_type_expression(
&mut self,
subscript: &ast::ExprSubscript,
@@ -4230,9 +4076,10 @@ impl<'db> TypeInferenceBuilder<'db> {
} = subscript;
match value_ty {
Type::KnownInstance(known_instance) => {
self.infer_parameterized_known_instance_type_expression(known_instance, slice)
}
Type::Instance(InstanceType {
class: _,
known: Some(known_instance),
}) => self.infer_parameterized_known_instance_type_expression(known_instance, slice),
_ => {
self.infer_type_expression(slice);
Type::Todo // TODO: generics
@@ -4242,11 +4089,11 @@ impl<'db> TypeInferenceBuilder<'db> {
fn infer_parameterized_known_instance_type_expression(
&mut self,
known_instance: KnownInstanceType,
known_instance: KnownInstance,
parameters: &ast::Expr,
) -> Type<'db> {
match known_instance {
KnownInstanceType::Literal => match self.infer_literal_parameter_type(parameters) {
KnownInstance::Literal => match self.infer_literal_parameter_type(parameters) {
Ok(ty) => ty,
Err(nodes) => {
for node in nodes {
@@ -4273,7 +4120,13 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO handle type aliases
ast::Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
let value_ty = self.infer_expression(value);
if matches!(value_ty, Type::KnownInstance(KnownInstanceType::Literal)) {
if matches!(
value_ty,
Type::Instance(InstanceType {
known: Some(KnownInstance::Literal),
..
})
) {
self.infer_literal_parameter_type(slice)?
} else {
return Err(vec![parameters]);
@@ -4541,8 +4394,7 @@ fn perform_membership_test_comparison<'db>(
// iteration-based membership test
match Type::Instance(right).iterate(db) {
IterationOutcome::Iterable { .. } => Some(KnownClass::Bool.to_instance(db)),
IterationOutcome::NotIterable { .. }
| IterationOutcome::PossiblyUnboundDunderIter { .. } => None,
IterationOutcome::NotIterable { .. } => None,
}
}
};
@@ -4681,7 +4533,7 @@ mod tests {
let file = system_path_to_file(db, filename).unwrap();
let diagnostics = check_types(db, file);
assert_diagnostic_messages(diagnostics, expected);
assert_diagnostic_messages(&diagnostics, expected);
}
#[test]

View File

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

View File

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

View File

@@ -6,7 +6,7 @@ mod text_document;
use lsp_types::{PositionEncodingKind, Url};
pub use notebook::NotebookDocument;
pub(crate) use range::{RangeExt, ToRangeExt};
pub(crate) use range::RangeExt;
pub(crate) use text_document::DocumentVersion;
pub use text_document::TextDocument;

View File

@@ -1,32 +1,13 @@
use super::notebook;
use super::PositionEncoding;
use lsp_types as types;
use ruff_notebook::NotebookIndex;
use ruff_source_file::LineIndex;
use ruff_source_file::OneIndexed;
use ruff_source_file::{LineIndex, SourceLocation};
use ruff_text_size::{TextRange, TextSize};
pub(crate) struct NotebookRange {
pub(crate) cell: notebook::CellId,
pub(crate) range: types::Range,
}
pub(crate) trait RangeExt {
fn to_text_range(&self, text: &str, index: &LineIndex, encoding: PositionEncoding)
-> TextRange;
}
pub(crate) trait ToRangeExt {
fn to_range(&self, text: &str, index: &LineIndex, encoding: PositionEncoding) -> types::Range;
fn to_notebook_range(
&self,
text: &str,
source_index: &LineIndex,
notebook_index: &NotebookIndex,
encoding: PositionEncoding,
) -> NotebookRange;
}
fn u32_index_to_usize(index: u32) -> usize {
usize::try_from(index).expect("u32 fits in usize")
}
@@ -94,61 +75,6 @@ impl RangeExt for lsp_types::Range {
}
}
impl ToRangeExt for TextRange {
fn to_range(&self, text: &str, index: &LineIndex, encoding: PositionEncoding) -> types::Range {
types::Range {
start: source_location_to_position(&offset_to_source_location(
self.start(),
text,
index,
encoding,
)),
end: source_location_to_position(&offset_to_source_location(
self.end(),
text,
index,
encoding,
)),
}
}
fn to_notebook_range(
&self,
text: &str,
source_index: &LineIndex,
notebook_index: &NotebookIndex,
encoding: PositionEncoding,
) -> NotebookRange {
let start = offset_to_source_location(self.start(), text, source_index, encoding);
let mut end = offset_to_source_location(self.end(), text, source_index, encoding);
let starting_cell = notebook_index.cell(start.row);
// weird edge case here - if the end of the range is where the newline after the cell got added (making it 'out of bounds')
// we need to move it one character back (which should place it at the end of the last line).
// we test this by checking if the ending offset is in a different (or nonexistent) cell compared to the cell of the starting offset.
if notebook_index.cell(end.row) != starting_cell {
end.row = end.row.saturating_sub(1);
end.column = offset_to_source_location(
self.end().checked_sub(1.into()).unwrap_or_default(),
text,
source_index,
encoding,
)
.column;
}
let start = source_location_to_position(&notebook_index.translate_location(&start));
let end = source_location_to_position(&notebook_index.translate_location(&end));
NotebookRange {
cell: starting_cell
.map(OneIndexed::to_zero_indexed)
.unwrap_or_default(),
range: types::Range { start, end },
}
}
}
/// Converts a UTF-16 code unit offset for a given line into a UTF-8 column number.
fn utf8_column_offset(utf16_code_unit_offset: u32, line: &str) -> TextSize {
let mut utf8_code_unit_offset = TextSize::new(0);
@@ -170,46 +96,3 @@ fn utf8_column_offset(utf16_code_unit_offset: u32, line: &str) -> TextSize {
utf8_code_unit_offset
}
fn offset_to_source_location(
offset: TextSize,
text: &str,
index: &LineIndex,
encoding: PositionEncoding,
) -> SourceLocation {
match encoding {
PositionEncoding::UTF8 => {
let row = index.line_index(offset);
let column = offset - index.line_start(row, text);
SourceLocation {
column: OneIndexed::from_zero_indexed(column.to_usize()),
row,
}
}
PositionEncoding::UTF16 => {
let row = index.line_index(offset);
let column = if index.is_ascii() {
(offset - index.line_start(row, text)).to_usize()
} else {
let up_to_line = &text[TextRange::new(index.line_start(row, text), offset)];
up_to_line.encode_utf16().count()
};
SourceLocation {
column: OneIndexed::from_zero_indexed(column),
row,
}
}
PositionEncoding::UTF32 => index.source_location(offset, text),
}
}
fn source_location_to_position(location: &SourceLocation) -> types::Position {
types::Position {
line: u32::try_from(location.row.to_zero_indexed()).expect("row usize fits in u32"),
character: u32::try_from(location.column.to_zero_indexed())
.expect("character usize fits in u32"),
}
}

View File

@@ -3,17 +3,15 @@ use std::borrow::Cow;
use lsp_types::request::DocumentDiagnosticRequest;
use lsp_types::{
Diagnostic, DiagnosticSeverity, DocumentDiagnosticParams, DocumentDiagnosticReport,
DocumentDiagnosticReportResult, FullDocumentDiagnosticReport, NumberOrString, Range,
DocumentDiagnosticReportResult, FullDocumentDiagnosticReport, Position, Range,
RelatedFullDocumentDiagnosticReport, Url,
};
use crate::edit::ToRangeExt;
use red_knot_workspace::db::RootDatabase;
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;
@@ -66,37 +64,36 @@ fn compute_diagnostics(snapshot: &DocumentSnapshot, db: &RootDatabase) -> Vec<Di
diagnostics
.as_slice()
.iter()
.map(|message| to_lsp_diagnostic(db, message, snapshot.encoding()))
.map(|message| to_lsp_diagnostic(message))
.collect()
}
fn to_lsp_diagnostic(
db: &dyn Db,
diagnostic: &dyn ruff_db::diagnostic::Diagnostic,
encoding: crate::PositionEncoding,
) -> Diagnostic {
let range = if let Some(range) = diagnostic.range() {
let index = line_index(db.upcast(), diagnostic.file());
let source = source_text(db.upcast(), diagnostic.file());
fn to_lsp_diagnostic(message: &str) -> Diagnostic {
let words = message.split(':').collect::<Vec<_>>();
range.to_range(&source, &index, encoding)
} else {
Range::default()
};
let severity = match diagnostic.severity() {
Severity::Info => DiagnosticSeverity::INFORMATION,
Severity::Error => DiagnosticSeverity::ERROR,
let (range, message) = match words.as_slice() {
[_, _, line, column, message] | [_, line, column, message] => {
let line = line.parse::<u32>().unwrap_or_default().saturating_sub(1);
let column = column.parse::<u32>().unwrap_or_default();
(
Range::new(
Position::new(line, column.saturating_sub(1)),
Position::new(line, column),
),
message.trim(),
)
}
_ => (Range::default(), message),
};
Diagnostic {
range,
severity: Some(severity),
severity: Some(DiagnosticSeverity::ERROR),
tags: None,
code: Some(NumberOrString::String(diagnostic.rule().to_string())),
code: None,
code_description: None,
source: Some("red-knot".into()),
message: diagnostic.message().into_owned(),
message: message.to_string(),
related_information: None,
data: None,
}

View File

@@ -15,6 +15,7 @@ red_knot_python_semantic = { workspace = true }
red_knot_vendored = { workspace = true }
ruff_db = { workspace = true }
ruff_index = { workspace = true }
ruff_python_parser = { workspace = true }
ruff_python_trivia = { workspace = true }
ruff_source_file = { workspace = true }
ruff_text_size = { workspace = true }

View File

@@ -2,10 +2,63 @@
//!
//! We don't assume that we will get the diagnostics in source order.
use ruff_db::diagnostic::Diagnostic;
use red_knot_python_semantic::types::TypeCheckDiagnostic;
use ruff_python_parser::ParseError;
use ruff_source_file::{LineIndex, OneIndexed};
use ruff_text_size::{Ranged, TextRange};
use std::borrow::Cow;
use std::ops::{Deref, Range};
pub(super) trait Diagnostic: std::fmt::Debug {
fn rule(&self) -> &str;
fn message(&self) -> Cow<str>;
fn range(&self) -> TextRange;
}
impl Diagnostic for TypeCheckDiagnostic {
fn rule(&self) -> &str {
TypeCheckDiagnostic::rule(self)
}
fn message(&self) -> Cow<str> {
TypeCheckDiagnostic::message(self).into()
}
fn range(&self) -> TextRange {
Ranged::range(self)
}
}
impl Diagnostic for ParseError {
fn rule(&self) -> &str {
"invalid-syntax"
}
fn message(&self) -> Cow<str> {
self.error.to_string().into()
}
fn range(&self) -> TextRange {
self.location
}
}
impl Diagnostic for Box<dyn Diagnostic> {
fn rule(&self) -> &str {
(**self).rule()
}
fn message(&self) -> Cow<str> {
(**self).message()
}
fn range(&self) -> TextRange {
(**self).range()
}
}
/// All diagnostics for one embedded Python file, sorted and grouped by start line number.
///
/// The diagnostics are kept in a flat vector, sorted by line number. A separate vector of
@@ -25,11 +78,7 @@ where
let mut diagnostics: Vec<_> = diagnostics
.into_iter()
.map(|diagnostic| DiagnosticWithLine {
line_number: diagnostic
.range()
.map_or(OneIndexed::from_zero_indexed(0), |range| {
line_index.line_index(range.start())
}),
line_number: line_index.line_index(diagnostic.range().start()),
diagnostic,
})
.collect();
@@ -144,8 +193,7 @@ struct DiagnosticWithLine<T> {
mod tests {
use crate::db::Db;
use crate::diagnostic::Diagnostic;
use ruff_db::diagnostic::Severity;
use ruff_db::files::{system_path_to_file, File};
use ruff_db::files::system_path_to_file;
use ruff_db::source::line_index;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_source_file::OneIndexed;
@@ -167,7 +215,7 @@ mod tests {
let diagnostics: Vec<_> = ranges
.into_iter()
.map(|range| DummyDiagnostic { range, file })
.map(|range| DummyDiagnostic { range })
.collect();
let sorted = super::SortedDiagnostics::new(diagnostics, &lines);
@@ -186,7 +234,6 @@ mod tests {
#[derive(Debug)]
struct DummyDiagnostic {
range: TextRange,
file: File,
}
impl Diagnostic for DummyDiagnostic {
@@ -198,16 +245,8 @@ mod tests {
"dummy".into()
}
fn file(&self) -> File {
self.file
}
fn range(&self) -> Option<TextRange> {
Some(self.range)
}
fn severity(&self) -> Severity {
Severity::Error
fn range(&self) -> TextRange {
self.range
}
}
}

View File

@@ -1,13 +1,14 @@
use crate::diagnostic::Diagnostic;
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::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;
use std::path::Path;
use std::sync::Arc;
mod assertion;
mod db;
@@ -93,15 +94,14 @@ fn run_test(db: &mut db::Db, test: &parser::MarkdownTest) -> Result<(), Failures
.iter()
.cloned()
.map(|error| {
let diagnostic: Box<dyn Diagnostic> =
Box::new(ParseDiagnostic::new(test_file.file, error));
let diagnostic: Box<dyn Diagnostic> = Box::new(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());
let diagnostic: Box<dyn Diagnostic> = Box::new(Arc::unwrap_or_clone(diagnostic));
diagnostic
}));

View File

@@ -2,9 +2,8 @@
//! mismatches.
use crate::assertion::{Assertion, ErrorAssertion, InlineFileAssertions};
use crate::db::Db;
use crate::diagnostic::SortedDiagnostics;
use crate::diagnostic::{Diagnostic, SortedDiagnostics};
use colored::Colorize;
use ruff_db::diagnostic::Diagnostic;
use ruff_db::files::File;
use ruff_db::source::{line_index, source_text, SourceText};
use ruff_source_file::{LineIndex, OneIndexed};
@@ -236,14 +235,9 @@ impl Matcher {
}
fn column<T: Diagnostic>(&self, diagnostic: &T) -> OneIndexed {
diagnostic
.range()
.map(|range| {
self.line_index
.source_location(range.start(), &self.source)
.column
})
.unwrap_or(OneIndexed::from_zero_indexed(0))
self.line_index
.source_location(diagnostic.range().start(), &self.source)
.column
}
/// Check if `assertion` matches any [`Diagnostic`]s in `unmatched`.
@@ -310,21 +304,22 @@ impl Matcher {
#[cfg(test)]
mod tests {
use super::FailuresByLine;
use ruff_db::diagnostic::{Diagnostic, Severity};
use ruff_db::files::{system_path_to_file, File};
use crate::diagnostic::Diagnostic;
use ruff_db::files::system_path_to_file;
use ruff_db::system::{DbWithTestSystem, SystemPathBuf};
use ruff_python_trivia::textwrap::dedent;
use ruff_source_file::OneIndexed;
use ruff_text_size::TextRange;
use std::borrow::Cow;
struct ExpectedDiagnostic {
#[derive(Clone, Debug)]
struct TestDiagnostic {
rule: &'static str,
message: &'static str,
range: TextRange,
}
impl ExpectedDiagnostic {
impl TestDiagnostic {
fn new(rule: &'static str, message: &'static str, offset: usize) -> Self {
let offset: u32 = offset.try_into().unwrap();
Self {
@@ -333,23 +328,6 @@ mod tests {
range: TextRange::new(offset.into(), (offset + 1).into()),
}
}
fn into_diagnostic(self, file: File) -> TestDiagnostic {
TestDiagnostic {
rule: self.rule,
message: self.message,
range: self.range,
file,
}
}
}
#[derive(Debug)]
struct TestDiagnostic {
rule: &'static str,
message: &'static str,
range: TextRange,
file: File,
}
impl Diagnostic for TestDiagnostic {
@@ -361,36 +339,19 @@ mod tests {
self.message.into()
}
fn file(&self) -> File {
self.file
}
fn range(&self) -> Option<TextRange> {
Some(self.range)
}
fn severity(&self) -> Severity {
Severity::Error
fn range(&self) -> TextRange {
self.range
}
}
fn get_result(
source: &str,
diagnostics: Vec<ExpectedDiagnostic>,
) -> Result<(), FailuresByLine> {
fn get_result(source: &str, diagnostics: Vec<TestDiagnostic>) -> Result<(), FailuresByLine> {
colored::control::set_override(false);
let mut db = crate::db::Db::setup(SystemPathBuf::from("/src"));
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)),
)
super::match_file(&db, file, diagnostics)
}
fn assert_fail(result: Result<(), FailuresByLine>, messages: &[(usize, &[&str])]) {
@@ -423,7 +384,7 @@ mod tests {
fn revealed_match() {
let result = get_result(
"x # revealed: Foo",
vec![ExpectedDiagnostic::new(
vec![TestDiagnostic::new(
"revealed-type",
"Revealed type is `Foo`",
0,
@@ -437,7 +398,7 @@ mod tests {
fn revealed_wrong_rule() {
let result = get_result(
"x # revealed: Foo",
vec![ExpectedDiagnostic::new(
vec![TestDiagnostic::new(
"not-revealed-type",
"Revealed type is `Foo`",
0,
@@ -460,11 +421,7 @@ mod tests {
fn revealed_wrong_message() {
let result = get_result(
"x # revealed: Foo",
vec![ExpectedDiagnostic::new(
"revealed-type",
"Something else",
0,
)],
vec![TestDiagnostic::new("revealed-type", "Something else", 0)],
);
assert_fail(
@@ -491,8 +448,8 @@ mod tests {
let result = get_result(
"x # revealed: Foo",
vec![
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Foo`", 0),
ExpectedDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
TestDiagnostic::new("revealed-type", "Revealed type is `Foo`", 0),
TestDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
],
);
@@ -503,11 +460,7 @@ mod tests {
fn revealed_match_with_only_undefined() {
let result = get_result(
"x # revealed: Foo",
vec![ExpectedDiagnostic::new(
"undefined-reveal",
"Doesn't matter",
0,
)],
vec![TestDiagnostic::new("undefined-reveal", "Doesn't matter", 0)],
);
assert_fail(result, &[(0, &["unmatched assertion: revealed: Foo"])]);
@@ -518,8 +471,8 @@ mod tests {
let result = get_result(
"x # revealed: Foo",
vec![
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Bar`", 0),
ExpectedDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
TestDiagnostic::new("revealed-type", "Revealed type is `Bar`", 0),
TestDiagnostic::new("undefined-reveal", "Doesn't matter", 0),
],
);
@@ -540,8 +493,8 @@ mod tests {
let result = get_result(
"reveal_type(1)",
vec![
ExpectedDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
TestDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
TestDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
],
);
@@ -563,8 +516,8 @@ mod tests {
let result = get_result(
"reveal_type(1) # error: [something-else]",
vec![
ExpectedDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
TestDiagnostic::new("undefined-reveal", "undefined reveal message", 0),
TestDiagnostic::new("revealed-type", "Revealed type is `Literal[1]`", 12),
],
);
@@ -593,7 +546,7 @@ mod tests {
fn error_match_rule() {
let result = get_result(
"x # error: [some-rule]",
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
vec![TestDiagnostic::new("some-rule", "Any message", 0)],
);
assert_ok(&result);
@@ -603,7 +556,7 @@ mod tests {
fn error_wrong_rule() {
let result = get_result(
"x # error: [some-rule]",
vec![ExpectedDiagnostic::new("anything", "Any message", 0)],
vec![TestDiagnostic::new("anything", "Any message", 0)],
);
assert_fail(
@@ -622,11 +575,7 @@ mod tests {
fn error_match_message() {
let result = get_result(
r#"x # error: "contains this""#,
vec![ExpectedDiagnostic::new(
"anything",
"message contains this",
0,
)],
vec![TestDiagnostic::new("anything", "message contains this", 0)],
);
assert_ok(&result);
@@ -636,7 +585,7 @@ mod tests {
fn error_wrong_message() {
let result = get_result(
r#"x # error: "contains this""#,
vec![ExpectedDiagnostic::new("anything", "Any message", 0)],
vec![TestDiagnostic::new("anything", "Any message", 0)],
);
assert_fail(
@@ -655,7 +604,7 @@ mod tests {
fn error_match_column_and_rule() {
let result = get_result(
"x # error: 1 [some-rule]",
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
vec![TestDiagnostic::new("some-rule", "Any message", 0)],
);
assert_ok(&result);
@@ -665,7 +614,7 @@ mod tests {
fn error_wrong_column() {
let result = get_result(
"x # error: 2 [rule]",
vec![ExpectedDiagnostic::new("rule", "Any message", 0)],
vec![TestDiagnostic::new("rule", "Any message", 0)],
);
assert_fail(
@@ -684,11 +633,7 @@ mod tests {
fn error_match_column_and_message() {
let result = get_result(
r#"x # error: 1 "contains this""#,
vec![ExpectedDiagnostic::new(
"anything",
"message contains this",
0,
)],
vec![TestDiagnostic::new("anything", "message contains this", 0)],
);
assert_ok(&result);
@@ -698,11 +643,7 @@ mod tests {
fn error_match_rule_and_message() {
let result = get_result(
r#"x # error: [a-rule] "contains this""#,
vec![ExpectedDiagnostic::new(
"a-rule",
"message contains this",
0,
)],
vec![TestDiagnostic::new("a-rule", "message contains this", 0)],
);
assert_ok(&result);
@@ -712,11 +653,7 @@ mod tests {
fn error_match_all() {
let result = get_result(
r#"x # error: 1 [a-rule] "contains this""#,
vec![ExpectedDiagnostic::new(
"a-rule",
"message contains this",
0,
)],
vec![TestDiagnostic::new("a-rule", "message contains this", 0)],
);
assert_ok(&result);
@@ -726,11 +663,7 @@ mod tests {
fn error_match_all_wrong_column() {
let result = get_result(
r#"x # error: 2 [some-rule] "contains this""#,
vec![ExpectedDiagnostic::new(
"some-rule",
"message contains this",
0,
)],
vec![TestDiagnostic::new("some-rule", "message contains this", 0)],
);
assert_fail(
@@ -749,7 +682,7 @@ mod tests {
fn error_match_all_wrong_rule() {
let result = get_result(
r#"x # error: 1 [some-rule] "contains this""#,
vec![ExpectedDiagnostic::new(
vec![TestDiagnostic::new(
"other-rule",
"message contains this",
0,
@@ -772,7 +705,7 @@ mod tests {
fn error_match_all_wrong_message() {
let result = get_result(
r#"x # error: 1 [some-rule] "contains this""#,
vec![ExpectedDiagnostic::new("some-rule", "Any message", 0)],
vec![TestDiagnostic::new("some-rule", "Any message", 0)],
);
assert_fail(
@@ -805,9 +738,9 @@ mod tests {
let result = get_result(
&source,
vec![
ExpectedDiagnostic::new("line-two", "msg", two),
ExpectedDiagnostic::new("line-three", "msg", three),
ExpectedDiagnostic::new("line-five", "msg", five),
TestDiagnostic::new("line-two", "msg", two),
TestDiagnostic::new("line-three", "msg", three),
TestDiagnostic::new("line-five", "msg", five),
],
);
@@ -836,8 +769,8 @@ mod tests {
let result = get_result(
&source,
vec![
ExpectedDiagnostic::new("line-one", "msg", one),
ExpectedDiagnostic::new("line-two", "msg", two),
TestDiagnostic::new("line-one", "msg", one),
TestDiagnostic::new("line-two", "msg", two),
],
);
@@ -857,8 +790,8 @@ mod tests {
let result = get_result(
&source,
vec![
ExpectedDiagnostic::new("one-rule", "msg", x),
ExpectedDiagnostic::new("other-rule", "msg", x),
TestDiagnostic::new("one-rule", "msg", x),
TestDiagnostic::new("other-rule", "msg", x),
],
);
@@ -878,8 +811,8 @@ mod tests {
let result = get_result(
&source,
vec![
ExpectedDiagnostic::new("one-rule", "msg", x),
ExpectedDiagnostic::new("one-rule", "msg", x),
TestDiagnostic::new("one-rule", "msg", x),
TestDiagnostic::new("one-rule", "msg", x),
],
);
@@ -899,9 +832,9 @@ mod tests {
let result = get_result(
&source,
vec![
ExpectedDiagnostic::new("one-rule", "msg", x),
ExpectedDiagnostic::new("other-rule", "msg", x),
ExpectedDiagnostic::new("third-rule", "msg", x),
TestDiagnostic::new("one-rule", "msg", x),
TestDiagnostic::new("other-rule", "msg", x),
TestDiagnostic::new("third-rule", "msg", x),
],
);
@@ -925,8 +858,8 @@ mod tests {
let result = get_result(
&source,
vec![
ExpectedDiagnostic::new("undefined-reveal", "msg", reveal),
ExpectedDiagnostic::new("revealed-type", "Revealed type is `Literal[5]`", reveal),
TestDiagnostic::new("undefined-reveal", "msg", reveal),
TestDiagnostic::new("revealed-type", "Revealed type is `Literal[5]`", reveal),
],
);
@@ -939,7 +872,7 @@ mod tests {
let x = source.find('x').unwrap();
let result = get_result(
source,
vec![ExpectedDiagnostic::new("some-rule", "some message", x)],
vec![TestDiagnostic::new("some-rule", "some message", x)],
);
assert_fail(
@@ -960,7 +893,7 @@ mod tests {
let x = source.find('x').unwrap();
let result = get_result(
source,
vec![ExpectedDiagnostic::new("some-rule", "some message", x)],
vec![TestDiagnostic::new("some-rule", "some message", x)],
);
assert_fail(

View File

@@ -6,7 +6,6 @@ use wasm_bindgen::prelude::*;
use red_knot_workspace::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 +110,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(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(result)
}
/// Returns the parsed AST for `path`

View File

@@ -19,6 +19,6 @@ fn check() {
assert_eq!(
result,
vec!["error[unresolved-import] /test.py:1:8 Cannot resolve import `random22`"]
vec!["/test.py:1:8: Cannot resolve import `random22`"]
);
}

View File

@@ -4,14 +4,14 @@ use std::sync::Arc;
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::{check_file, Workspace, WorkspaceMetadata};
mod changes;
#[salsa::db]
@@ -51,11 +51,11 @@ 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<String>, 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<String>, Cancelled> {
let _span = tracing::debug_span!("check_file", file=%file.path(self)).entered();
self.with_db(|db| check_file(db, file))

View File

@@ -1,23 +1,23 @@
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 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::{line_index, source_text, SourceDiagnostic};
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 ruff_text_size::Ranged;
use crate::db::Db;
use crate::db::RootDatabase;
use crate::workspace::files::{Index, Indexed, IndexedIter, PackageFiles};
mod files;
mod metadata;
@@ -188,7 +188,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<String> {
let workspace_span = tracing::debug_span!("check_workspace");
let _span = workspace_span.enter();
@@ -378,31 +378,47 @@ 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();
#[salsa::tracked]
pub(super) fn check_file(db: &dyn Db, file: File) -> Vec<String> {
tracing::debug!("Checking file '{path}'", path = file.path(db));
let mut diagnostics = Vec::new();
let source_diagnostics = source_text::accumulated::<SourceDiagnostic>(db.upcast(), file);
// TODO(micha): Consider using a single accumulator for all diagnostics
diagnostics.extend(
source_diagnostics
.iter()
.map(std::string::ToString::to_string),
);
// Abort checking if there are IO errors.
let source = source_text(db.upcast(), file);
if let Some(read_error) = source.read_error() {
diagnostics.push(Box::new(IOErrorDiagnostic {
file,
error: read_error.clone(),
}));
if source.has_read_error() {
return diagnostics;
}
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
}));
diagnostics.extend(check_types(db.upcast(), file).iter().map(|diagnostic| {
let boxed: Box<dyn Diagnostic> = Box::new(diagnostic.clone());
boxed
}));
if !parsed.errors().is_empty() {
let path = file.path(db);
let line_index = line_index(db.upcast(), file);
diagnostics.extend(parsed.errors().iter().map(|err| {
let source_location = line_index.source_location(err.location.start(), source.as_str());
format!("{path}:{source_location}: {message}", message = err.error)
}));
}
diagnostics.sort_unstable_by_key(|diagnostic| diagnostic.range().unwrap_or_default().start());
for diagnostic in check_types(db.upcast(), file) {
let index = line_index(db.upcast(), diagnostic.file());
let location = index.source_location(diagnostic.start(), source.as_str());
diagnostics.push(format!(
"{path}:{location}: {message}",
path = file.path(db),
message = diagnostic.message()
));
}
diagnostics
}
@@ -517,45 +533,17 @@ 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 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::testing::assert_function_query_was_not_run;
use crate::db::tests::TestDb;
use crate::workspace::check_file;
#[test]
fn check_file_skips_type_checking_when_file_cant_be_read() -> ruff_db::system::Result<()> {
let mut db = TestDb::new();
@@ -570,10 +558,7 @@ mod tests {
assert_eq!(source_text(&db, file).as_str(), "");
assert_eq!(
check_file(&db, file)
.into_iter()
.map(|diagnostic| diagnostic.message().into_owned())
.collect::<Vec<_>>(),
check_file(&db, file),
vec!["Failed to read file: No such file or directory".to_string()]
);
@@ -585,13 +570,7 @@ mod tests {
db.write_file(path, "").unwrap();
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!(check_file(&db, file), vec![] as Vec<String>);
Ok(())
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.7.3"
version = "0.7.2"
publish = true
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -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};
@@ -25,32 +24,32 @@ const TOMLLIB_312_URL: &str = "https://raw.githubusercontent.com/python/cpython/
static EXPECTED_DIAGNOSTICS: &[&str] = &[
// We don't support `*` imports yet:
"error[unresolved-import] /src/tomllib/_parser.py:7:29 Module `collections.abc` has no member `Iterable`",
"/src/tomllib/_parser.py:7:29: Module `collections.abc` has no member `Iterable`",
// We don't support terminal statements in control flow yet:
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:66:18 Name `s` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:98:12 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:101:12 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
"error[conflicting-declarations] /src/tomllib/_parser.py:108:17 Conflicting declared types for `second_char`: Unknown, str | None",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
"error[call-possibly-unbound-method] /src/tomllib/_parser.py:246:15 Method `__class_getitem__` of type `Literal[frozenset]` is possibly unbound",
"error[conflicting-declarations] /src/tomllib/_parser.py:267:9 Conflicting declared types for `char`: Unknown, str | None",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",
"error[conflicting-declarations] /src/tomllib/_parser.py:364:9 Conflicting declared types for `char`: Unknown, str | None",
"error[conflicting-declarations] /src/tomllib/_parser.py:381:13 Conflicting declared types for `char`: Unknown, str | None",
"error[conflicting-declarations] /src/tomllib/_parser.py:395:9 Conflicting declared types for `char`: Unknown, str | None",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:453:24 Name `nest` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:455:9 Name `nest` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:482:16 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:566:12 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:573:12 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined",
"error[conflicting-declarations] /src/tomllib/_parser.py:590:9 Conflicting declared types for `char`: Unknown, str | None",
"error[possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined",
"error[invalid-base] /src/tomllib/_parser.py:692:8354 Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)",
"/src/tomllib/_parser.py:246:15: Method `__class_getitem__` of type `Literal[frozenset]` is possibly unbound",
"/src/tomllib/_parser.py:692:8354: Invalid class base with type `GenericAlias` (all bases must be a class, `Any`, `Unknown` or `Todo`)",
"/src/tomllib/_parser.py:66:18: Name `s` used when possibly not defined",
"/src/tomllib/_parser.py:98:12: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:101:12: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:104:14: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:108:17: Conflicting declared types for `second_char`: Unknown, str | None",
"/src/tomllib/_parser.py:115:14: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:126:12: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:267:9: Conflicting declared types for `char`: Unknown, str | None",
"/src/tomllib/_parser.py:348:20: Name `nest` used when possibly not defined",
"/src/tomllib/_parser.py:353:5: Name `nest` used when possibly not defined",
"/src/tomllib/_parser.py:364:9: Conflicting declared types for `char`: Unknown, str | None",
"/src/tomllib/_parser.py:381:13: Conflicting declared types for `char`: Unknown, str | None",
"/src/tomllib/_parser.py:395:9: Conflicting declared types for `char`: Unknown, str | None",
"/src/tomllib/_parser.py:453:24: Name `nest` used when possibly not defined",
"/src/tomllib/_parser.py:455:9: Name `nest` used when possibly not defined",
"/src/tomllib/_parser.py:482:16: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:566:12: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:573:12: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:579:12: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:580:63: Name `char` used when possibly not defined",
"/src/tomllib/_parser.py:590:9: Conflicting declared types for `char`: Unknown, str | None",
"/src/tomllib/_parser.py:629:38: Name `datetime_obj` used when possibly not defined",
];
fn get_test_file(name: &str) -> TestFile {
@@ -124,14 +123,7 @@ fn setup_rayon() {
fn benchmark_incremental(criterion: &mut Criterion) {
fn setup() -> Case {
let case = setup_case();
let result: Vec<_> = case
.db
.check()
.unwrap()
.into_iter()
.map(|diagnostic| diagnostic.display(&case.db).to_string())
.collect();
let result = case.db.check().unwrap();
assert_eq!(result, EXPECTED_DIAGNOSTICS);
@@ -158,7 +150,7 @@ fn benchmark_incremental(criterion: &mut Criterion) {
let result = db.check().unwrap();
assert_eq!(result.len(), EXPECTED_DIAGNOSTICS.len());
assert_eq!(result, EXPECTED_DIAGNOSTICS);
}
setup_rayon();
@@ -176,12 +168,7 @@ fn benchmark_cold(criterion: &mut Criterion) {
setup_case,
|case| {
let Case { db, .. } = case;
let result: Vec<_> = db
.check()
.unwrap()
.into_iter()
.map(|diagnostic| diagnostic.display(db).to_string())
.collect();
let result = db.check().unwrap();
assert_eq!(result, EXPECTED_DIAGNOSTICS);
},

View File

@@ -1,180 +0,0 @@
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;
fn message(&self) -> std::borrow::Cow<str>;
fn file(&self) -> File;
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)]
pub enum Severity {
Info,
Error,
}
pub struct DisplayDiagnostic<'db> {
db: &'db dyn Db,
diagnostic: &'db dyn Diagnostic,
}
impl<'db> DisplayDiagnostic<'db> {
pub fn new(db: &'db dyn Db, diagnostic: &'db dyn Diagnostic) -> Self {
Self { db, diagnostic }
}
}
impl std::fmt::Display for DisplayDiagnostic<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.diagnostic.severity() {
Severity::Info => f.write_str("info")?,
Severity::Error => f.write_str("error")?,
}
write!(
f,
"[{rule}] {path}",
rule = self.diagnostic.rule(),
path = self.diagnostic.file().path(self.db)
)?;
if let Some(range) = self.diagnostic.range() {
let index = line_index(self.db, self.diagnostic.file());
let source = source_text(self.db, self.diagnostic.file());
let start = index.source_location(range.start(), &source);
write!(f, ":{line}:{col}", line = start.row, col = start.column)?;
}
write!(f, " {message}", message = self.diagnostic.message())
}
}
impl<T> Diagnostic for Box<T>
where
T: 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()
}
}
impl<T> Diagnostic for std::sync::Arc<T>
where
T: Diagnostic,
{
fn rule(&self) -> &str {
(**self).rule()
}
fn message(&self) -> std::borrow::Cow<str> {
(**self).message()
}
fn file(&self) -> File {
(**self).file()
}
fn range(&self) -> Option<TextRange> {
(**self).range()
}
fn severity(&self) -> Severity {
(**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
}
}

View File

@@ -6,7 +6,6 @@ use crate::files::Files;
use crate::system::System;
use crate::vendored::VendoredFileSystem;
pub mod diagnostic;
pub mod display;
pub mod file_revision;
pub mod files;

View File

@@ -1,7 +1,9 @@
use std::fmt::Formatter;
use std::ops::Deref;
use std::sync::Arc;
use countme::Count;
use salsa::Accumulator;
use ruff_notebook::Notebook;
use ruff_python_ast::PySourceType;
@@ -15,14 +17,16 @@ 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;
SourceDiagnostic(Arc::new(SourceTextError::FailedToReadNotebook(error)))
.accumulate(db);
Notebook::empty()
})
.into()
@@ -31,7 +35,8 @@ 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;
SourceDiagnostic(Arc::new(SourceTextError::FailedToReadFile(error))).accumulate(db);
String::new()
})
.into()
@@ -40,7 +45,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 +98,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 +132,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 +153,21 @@ impl From<Notebook> for SourceTextKind {
}
}
#[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)]
#[salsa::accumulator]
pub struct SourceDiagnostic(Arc<SourceTextError>);
impl std::fmt::Display for SourceDiagnostic {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
#[derive(Debug, thiserror::Error)]
pub enum SourceTextError {
#[error("Failed to read notebook: {0}`")]
FailedToReadNotebook(String),
FailedToReadNotebook(#[from] ruff_notebook::NotebookError),
#[error("Failed to read file: {0}")]
FailedToReadFile(String),
FailedToReadFile(#[from] std::io::Error),
}
/// Computes the [`LineIndex`] for `file`.

View 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 }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()));
}
}

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@ use crate::checkers::ast::Checker;
/// ```
///
/// ## References
/// - [PEP 484 Type Hints](https://peps.python.org/pep-0484/)
/// - [PEP 484](https://peps.python.org/pep-0484/)
#[violation]
pub struct UnusedAnnotation {
name: String,

View File

@@ -32,7 +32,7 @@ use crate::checkers::ast::Checker;
///
/// ## References
/// - [Python documentation: Await expression](https://docs.python.org/3/reference/expressions.html#await)
/// - [PEP 492: Await Expression](https://peps.python.org/pep-0492/#await-expression)
/// - [PEP 492](https://peps.python.org/pep-0492/#await-expression)
#[violation]
pub struct AwaitOutsideAsync;

View File

@@ -13,7 +13,7 @@ const BIDI_UNICODE: [char; 10] = [
'\u{2068}', //{FIRST STRONG ISOLATE}
'\u{2069}', //{POP DIRECTIONAL ISOLATE}
// The following was part of PEP 672:
// https://peps.python.org/pep-0672/
// https://www.python.org/dev/peps/pep-0672/
// so the list above might not be complete
'\u{200F}', //{RIGHT-TO-LEFT MARK}
// We don't use
@@ -40,7 +40,7 @@ const BIDI_UNICODE: [char; 10] = [
/// ```
///
/// ## References
/// - [PEP 672: Bidirectional Text](https://peps.python.org/pep-0672/#bidirectional-text)
/// - [PEP 672](https://peps.python.org/pep-0672/#bidirectional-text)
#[violation]
pub struct BidirectionalUnicode;

View File

@@ -30,8 +30,8 @@ use crate::fix::snippet::SourceCodeSnippet;
/// ```
///
/// ## References
/// - [Python documentation: `max`](https://docs.python.org/3/library/functions.html#max)
/// - [Python documentation: `min`](https://docs.python.org/3/library/functions.html#min)
/// - [Python documentation: max function](https://docs.python.org/3/library/functions.html#max)
/// - [Python documentation: min function](https://docs.python.org/3/library/functions.html#min)
#[violation]
pub struct IfStmtMinMax {
min_max: MinMax,

View File

@@ -46,8 +46,8 @@ use crate::checkers::ast::Checker;
/// - [PEP 8: Naming Conventions](https://peps.python.org/pep-0008/#naming-conventions)
/// - [Semantic Versioning](https://semver.org/)
///
/// [PEP 8]: https://peps.python.org/pep-0008/
/// [PEP 420]: https://peps.python.org/pep-0420/
/// [PEP 8]: https://www.python.org/dev/peps/pep-0008/
/// [PEP 420]: https://www.python.org/dev/peps/pep-0420/
#[violation]
pub struct ImportPrivateName {
name: String,

View File

@@ -9,8 +9,7 @@ use ruff_text_size::Ranged;
/// Checks for import statements that import the current module.
///
/// ## Why is this bad?
/// Importing a module from itself is a circular dependency and results
/// in an `ImportError` exception.
/// Importing a module from itself is a circular dependency.
///
/// ## Example
///

View File

@@ -14,7 +14,7 @@ use crate::checkers::ast::Checker;
/// ## Why is this bad?
/// The `global` declaration applies to the entire scope. Using a name that's
/// declared as `global` in a given scope prior to the relevant `global`
/// declaration is a `SyntaxError`.
/// declaration is a syntax error.
///
/// ## Example
/// ```python

View File

@@ -28,15 +28,6 @@ use crate::checkers::ast::Checker;
/// def greeting():
/// print("Greetings friend!")
/// ```
///
/// or
///
/// ```python
/// class Person:
/// @staticmethod
/// def greeting():
/// print("Greetings friend!")
/// ```
#[violation]
pub struct NoSelfUse {
method_name: String,

View File

@@ -27,7 +27,7 @@ use ruff_macros::{derive_message_formats, violation};
///
/// ## References
/// - [Python documentation: The `nonlocal` statement](https://docs.python.org/3/reference/simple_stmts.html#nonlocal)
/// - [PEP 3104 Access to Names in Outer Scopes](https://peps.python.org/pep-3104/)
/// - [PEP 3104](https://peps.python.org/pep-3104/)
#[violation]
pub struct NonlocalWithoutBinding {
pub(crate) name: String,

View File

@@ -41,7 +41,7 @@ use crate::rules::pylint::helpers::type_param_name;
/// - [PEP 483 The Theory of Type Hints: Covariance and Contravariance](https://peps.python.org/pep-0483/#covariance-and-contravariance)
/// - [PEP 484 Type Hints: Covariance and contravariance](https://peps.python.org/pep-0484/#covariance-and-contravariance)
///
/// [PEP 484]: https://peps.python.org/pep-0484/
/// [PEP 484]: https://www.python.org/dev/peps/pep-0484/
#[violation]
pub struct TypeNameIncorrectVariance {
kind: VarKind,

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