mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-27 22:04:45 +00:00
Implement runtime-import-in-type-checking-block (TYP004) (#2146)
This commit is contained in:
parent
deff503932
commit
0758049e49
29 changed files with 364 additions and 40 deletions
|
@ -1202,6 +1202,7 @@ For more, see [flake8-type-checking](https://pypi.org/project/flake8-type-checki
|
||||||
|
|
||||||
| Code | Name | Message | Fix |
|
| Code | Name | Message | Fix |
|
||||||
| ---- | ---- | ------- | --- |
|
| ---- | ---- | ------- | --- |
|
||||||
|
| TYP004 | runtime-import-in-type-checking-block | Move import `{}` out of type-checking block. Import is used for more than type hinting. | |
|
||||||
| TYP005 | empty-type-checking-block | Found empty type-checking block | |
|
| TYP005 | empty-type-checking-block | Found empty type-checking block | |
|
||||||
|
|
||||||
### tryceratops (TRY)
|
### tryceratops (TRY)
|
||||||
|
|
5
resources/test/fixtures/flake8_type_checking/TYP004_1.py
vendored
Normal file
5
resources/test/fixtures/flake8_type_checking/TYP004_1.py
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from datetime import datetime
|
||||||
|
x = datetime
|
8
resources/test/fixtures/flake8_type_checking/TYP004_2.py
vendored
Normal file
8
resources/test/fixtures/flake8_type_checking/TYP004_2.py
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
|
def example():
|
||||||
|
return date()
|
6
resources/test/fixtures/flake8_type_checking/TYP004_3.py
vendored
Normal file
6
resources/test/fixtures/flake8_type_checking/TYP004_3.py
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
CustomType = Any
|
11
resources/test/fixtures/flake8_type_checking/TYP004_4.py
vendored
Normal file
11
resources/test/fixtures/flake8_type_checking/TYP004_4.py
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from typing import TYPE_CHECKING, Type
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def example(*args: Any, **kwargs: Any):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
my_type: Type[Any] | Any
|
8
resources/test/fixtures/flake8_type_checking/TYP004_5.py
vendored
Normal file
8
resources/test/fixtures/flake8_type_checking/TYP004_5.py
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import List, Sequence, Set
|
||||||
|
|
||||||
|
|
||||||
|
def example(a: List[int], /, b: Sequence[int], *, c: Set[int]):
|
||||||
|
return
|
10
resources/test/fixtures/flake8_type_checking/TYP004_6.py
vendored
Normal file
10
resources/test/fixtures/flake8_type_checking/TYP004_6.py
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
|
||||||
|
def example():
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
x = DataFrame
|
10
resources/test/fixtures/flake8_type_checking/TYP004_7.py
vendored
Normal file
10
resources/test/fixtures/flake8_type_checking/TYP004_7.py
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import AsyncIterator, List
|
||||||
|
|
||||||
|
|
||||||
|
class Example:
|
||||||
|
async def example(self) -> AsyncIterator[List[str]]:
|
||||||
|
yield 0
|
7
resources/test/fixtures/flake8_type_checking/TYP004_8.py
vendored
Normal file
7
resources/test/fixtures/flake8_type_checking/TYP004_8.py
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
d = WeakKeyDictionary["Any", "Any"]()
|
|
@ -1784,6 +1784,7 @@
|
||||||
"TYP",
|
"TYP",
|
||||||
"TYP0",
|
"TYP0",
|
||||||
"TYP00",
|
"TYP00",
|
||||||
|
"TYP004",
|
||||||
"TYP005",
|
"TYP005",
|
||||||
"UP",
|
"UP",
|
||||||
"UP0",
|
"UP0",
|
||||||
|
|
|
@ -33,6 +33,10 @@ impl Range {
|
||||||
pub fn from_located<T>(located: &Located<T>) -> Self {
|
pub fn from_located<T>(located: &Located<T>) -> Self {
|
||||||
Range::new(located.location, located.end_location.unwrap())
|
Range::new(located.location, located.end_location.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, other: &Range) -> bool {
|
||||||
|
self.location <= other.location && self.end_location >= other.end_location
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -130,8 +134,22 @@ pub struct Binding<'a> {
|
||||||
/// The statement in which the [`Binding`] was defined.
|
/// The statement in which the [`Binding`] was defined.
|
||||||
pub source: Option<RefEquality<'a, Stmt>>,
|
pub source: Option<RefEquality<'a, Stmt>>,
|
||||||
/// Tuple of (scope index, range) indicating the scope and range at which
|
/// Tuple of (scope index, range) indicating the scope and range at which
|
||||||
/// the binding was last used.
|
/// the binding was last used in a runtime context.
|
||||||
pub used: Option<(usize, Range)>,
|
pub runtime_usage: Option<(usize, Range)>,
|
||||||
|
/// Tuple of (scope index, range) indicating the scope and range at which
|
||||||
|
/// the binding was last used in a typing-time context.
|
||||||
|
pub typing_usage: Option<(usize, Range)>,
|
||||||
|
/// Tuple of (scope index, range) indicating the scope and range at which
|
||||||
|
/// the binding was last used in a synthetic context. This is used for
|
||||||
|
/// (e.g.) `__future__` imports, explicit re-exports, and other bindings
|
||||||
|
/// that should be considered used even if they're never referenced.
|
||||||
|
pub synthetic_usage: Option<(usize, Range)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub enum UsageContext {
|
||||||
|
Runtime,
|
||||||
|
Typing,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pyflakes defines the following binding hierarchy (via inheritance):
|
// Pyflakes defines the following binding hierarchy (via inheritance):
|
||||||
|
@ -152,6 +170,19 @@ pub struct Binding<'a> {
|
||||||
// FutureImportation
|
// FutureImportation
|
||||||
|
|
||||||
impl<'a> Binding<'a> {
|
impl<'a> Binding<'a> {
|
||||||
|
pub fn mark_used(&mut self, scope: usize, range: Range, context: UsageContext) {
|
||||||
|
match context {
|
||||||
|
UsageContext::Runtime => self.runtime_usage = Some((scope, range)),
|
||||||
|
UsageContext::Typing => self.typing_usage = Some((scope, range)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn used(&self) -> bool {
|
||||||
|
self.runtime_usage.is_some()
|
||||||
|
|| self.synthetic_usage.is_some()
|
||||||
|
|| self.typing_usage.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_definition(&self) -> bool {
|
pub fn is_definition(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self.kind,
|
self.kind,
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::ast::operations::extract_all_names;
|
||||||
use crate::ast::relocate::relocate_expr;
|
use crate::ast::relocate::relocate_expr;
|
||||||
use crate::ast::types::{
|
use crate::ast::types::{
|
||||||
Binding, BindingKind, CallPath, ClassDef, FunctionDef, Lambda, Node, Range, RefEquality, Scope,
|
Binding, BindingKind, CallPath, ClassDef, FunctionDef, Lambda, Node, Range, RefEquality, Scope,
|
||||||
ScopeKind,
|
ScopeKind, UsageContext,
|
||||||
};
|
};
|
||||||
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
use crate::ast::visitor::{walk_excepthandler, Visitor};
|
||||||
use crate::ast::{branch_detection, cast, helpers, operations, visitor};
|
use crate::ast::{branch_detection, cast, helpers, operations, visitor};
|
||||||
|
@ -92,12 +92,14 @@ pub struct Checker<'a> {
|
||||||
in_deferred_type_definition: bool,
|
in_deferred_type_definition: bool,
|
||||||
in_literal: bool,
|
in_literal: bool,
|
||||||
in_subscript: bool,
|
in_subscript: bool,
|
||||||
|
in_type_checking_block: bool,
|
||||||
seen_import_boundary: bool,
|
seen_import_boundary: bool,
|
||||||
futures_allowed: bool,
|
futures_allowed: bool,
|
||||||
annotations_future_enabled: bool,
|
annotations_future_enabled: bool,
|
||||||
except_handlers: Vec<Vec<Vec<&'a str>>>,
|
except_handlers: Vec<Vec<Vec<&'a str>>>,
|
||||||
// Check-specific state.
|
// Check-specific state.
|
||||||
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
|
pub(crate) flake8_bugbear_seen: Vec<&'a Expr>,
|
||||||
|
pub(crate) type_checking_blocks: Vec<&'a Stmt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Checker<'a> {
|
impl<'a> Checker<'a> {
|
||||||
|
@ -149,12 +151,14 @@ impl<'a> Checker<'a> {
|
||||||
in_deferred_type_definition: false,
|
in_deferred_type_definition: false,
|
||||||
in_literal: false,
|
in_literal: false,
|
||||||
in_subscript: false,
|
in_subscript: false,
|
||||||
|
in_type_checking_block: false,
|
||||||
seen_import_boundary: false,
|
seen_import_boundary: false,
|
||||||
futures_allowed: true,
|
futures_allowed: true,
|
||||||
annotations_future_enabled: path.extension().map_or(false, |ext| ext == "pyi"),
|
annotations_future_enabled: path.extension().map_or(false, |ext| ext == "pyi"),
|
||||||
except_handlers: vec![],
|
except_handlers: vec![],
|
||||||
// Check-specific state.
|
// Check-specific state.
|
||||||
flake8_bugbear_seen: vec![],
|
flake8_bugbear_seen: vec![],
|
||||||
|
type_checking_blocks: vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -316,6 +320,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let prev_in_type_checking_block = self.in_type_checking_block;
|
||||||
|
|
||||||
// Pre-visit.
|
// Pre-visit.
|
||||||
match &stmt.node {
|
match &stmt.node {
|
||||||
StmtKind::Global { names } => {
|
StmtKind::Global { names } => {
|
||||||
|
@ -329,7 +335,9 @@ where
|
||||||
let index = self.bindings.len();
|
let index = self.bindings.len();
|
||||||
self.bindings.push(Binding {
|
self.bindings.push(Binding {
|
||||||
kind: BindingKind::Global,
|
kind: BindingKind::Global,
|
||||||
used: usage,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: usage,
|
||||||
|
typing_usage: None,
|
||||||
range: *range,
|
range: *range,
|
||||||
source: Some(RefEquality(stmt)),
|
source: Some(RefEquality(stmt)),
|
||||||
});
|
});
|
||||||
|
@ -355,7 +363,9 @@ where
|
||||||
let index = self.bindings.len();
|
let index = self.bindings.len();
|
||||||
self.bindings.push(Binding {
|
self.bindings.push(Binding {
|
||||||
kind: BindingKind::Nonlocal,
|
kind: BindingKind::Nonlocal,
|
||||||
used: usage,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: usage,
|
||||||
|
typing_usage: None,
|
||||||
range: *range,
|
range: *range,
|
||||||
source: Some(RefEquality(stmt)),
|
source: Some(RefEquality(stmt)),
|
||||||
});
|
});
|
||||||
|
@ -369,7 +379,7 @@ where
|
||||||
for index in self.scope_stack.iter().skip(1).rev().skip(1) {
|
for index in self.scope_stack.iter().skip(1).rev().skip(1) {
|
||||||
if let Some(index) = self.scopes[*index].values.get(&name.as_str()) {
|
if let Some(index) = self.scopes[*index].values.get(&name.as_str()) {
|
||||||
exists = true;
|
exists = true;
|
||||||
self.bindings[*index].used = usage;
|
self.bindings[*index].runtime_usage = usage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -667,7 +677,9 @@ where
|
||||||
name,
|
name,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::FunctionDefinition,
|
kind: BindingKind::FunctionDefinition,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(stmt),
|
range: Range::from_located(stmt),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -819,7 +831,9 @@ where
|
||||||
name,
|
name,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::SubmoduleImportation(name, full_name),
|
kind: BindingKind::SubmoduleImportation(name, full_name),
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(alias),
|
range: Range::from_located(alias),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -838,9 +852,10 @@ where
|
||||||
name,
|
name,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Importation(name, full_name),
|
kind: BindingKind::Importation(name, full_name),
|
||||||
|
runtime_usage: None,
|
||||||
// Treat explicit re-export as usage (e.g., `import applications
|
// Treat explicit re-export as usage (e.g., `import applications
|
||||||
// as applications`).
|
// as applications`).
|
||||||
used: if alias
|
synthetic_usage: if alias
|
||||||
.node
|
.node
|
||||||
.asname
|
.asname
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -857,6 +872,7 @@ where
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(alias),
|
range: Range::from_located(alias),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -1086,8 +1102,9 @@ where
|
||||||
name,
|
name,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::FutureImportation,
|
kind: BindingKind::FutureImportation,
|
||||||
|
runtime_usage: None,
|
||||||
// Always mark `__future__` imports as used.
|
// Always mark `__future__` imports as used.
|
||||||
used: Some((
|
synthetic_usage: Some((
|
||||||
self.scopes[*(self
|
self.scopes[*(self
|
||||||
.scope_stack
|
.scope_stack
|
||||||
.last()
|
.last()
|
||||||
|
@ -1095,6 +1112,7 @@ where
|
||||||
.id,
|
.id,
|
||||||
Range::from_located(alias),
|
Range::from_located(alias),
|
||||||
)),
|
)),
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(alias),
|
range: Range::from_located(alias),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -1128,7 +1146,9 @@ where
|
||||||
"*",
|
"*",
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::StarImportation(*level, module.clone()),
|
kind: BindingKind::StarImportation(*level, module.clone()),
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(stmt),
|
range: Range::from_located(stmt),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -1182,9 +1202,10 @@ where
|
||||||
name,
|
name,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::FromImportation(name, full_name),
|
kind: BindingKind::FromImportation(name, full_name),
|
||||||
|
runtime_usage: None,
|
||||||
// Treat explicit re-export as usage (e.g., `from .applications
|
// Treat explicit re-export as usage (e.g., `from .applications
|
||||||
// import FastAPI as FastAPI`).
|
// import FastAPI as FastAPI`).
|
||||||
used: if alias
|
synthetic_usage: if alias
|
||||||
.node
|
.node
|
||||||
.asname
|
.asname
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -1201,6 +1222,7 @@ where
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
},
|
},
|
||||||
|
typing_usage: None,
|
||||||
range,
|
range,
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -1374,12 +1396,16 @@ where
|
||||||
self.handle_node_load(target);
|
self.handle_node_load(target);
|
||||||
}
|
}
|
||||||
StmtKind::If { test, body, orelse } => {
|
StmtKind::If { test, body, orelse } => {
|
||||||
if self.settings.rules.enabled(&Rule::IfTuple) {
|
if flake8_type_checking::helpers::is_type_checking_block(self, test) {
|
||||||
pyflakes::rules::if_tuple(self, stmt, test);
|
|
||||||
}
|
|
||||||
if self.settings.rules.enabled(&Rule::EmptyTypeCheckingBlock) {
|
if self.settings.rules.enabled(&Rule::EmptyTypeCheckingBlock) {
|
||||||
flake8_type_checking::rules::empty_type_checking_block(self, test, body);
|
flake8_type_checking::rules::empty_type_checking_block(self, test, body);
|
||||||
}
|
}
|
||||||
|
self.in_type_checking_block = true;
|
||||||
|
self.type_checking_blocks.push(stmt);
|
||||||
|
}
|
||||||
|
if self.settings.rules.enabled(&Rule::IfTuple) {
|
||||||
|
pyflakes::rules::if_tuple(self, stmt, test);
|
||||||
|
}
|
||||||
if self.settings.rules.enabled(&Rule::NestedIfStatements) {
|
if self.settings.rules.enabled(&Rule::NestedIfStatements) {
|
||||||
flake8_simplify::rules::nested_if_statements(
|
flake8_simplify::rules::nested_if_statements(
|
||||||
self,
|
self,
|
||||||
|
@ -1709,7 +1735,9 @@ where
|
||||||
let index = self.bindings.len();
|
let index = self.bindings.len();
|
||||||
self.bindings.push(Binding {
|
self.bindings.push(Binding {
|
||||||
kind: BindingKind::Assignment,
|
kind: BindingKind::Assignment,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(stmt),
|
range: Range::from_located(stmt),
|
||||||
source: Some(RefEquality(stmt)),
|
source: Some(RefEquality(stmt)),
|
||||||
});
|
});
|
||||||
|
@ -1770,7 +1798,9 @@ where
|
||||||
let index = self.bindings.len();
|
let index = self.bindings.len();
|
||||||
self.bindings.push(Binding {
|
self.bindings.push(Binding {
|
||||||
kind: BindingKind::Assignment,
|
kind: BindingKind::Assignment,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(stmt),
|
range: Range::from_located(stmt),
|
||||||
source: Some(RefEquality(stmt)),
|
source: Some(RefEquality(stmt)),
|
||||||
});
|
});
|
||||||
|
@ -1831,6 +1861,7 @@ where
|
||||||
}
|
}
|
||||||
_ => visitor::walk_stmt(self, stmt),
|
_ => visitor::walk_stmt(self, stmt),
|
||||||
};
|
};
|
||||||
|
self.in_type_checking_block = prev_in_type_checking_block;
|
||||||
self.visible_scope = prev_visible_scope;
|
self.visible_scope = prev_visible_scope;
|
||||||
|
|
||||||
// Post-visit.
|
// Post-visit.
|
||||||
|
@ -1844,7 +1875,9 @@ where
|
||||||
name,
|
name,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::ClassDefinition,
|
kind: BindingKind::ClassDefinition,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(stmt),
|
range: Range::from_located(stmt),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -3414,7 +3447,7 @@ where
|
||||||
[*(self.scope_stack.last().expect("No current scope found"))];
|
[*(self.scope_stack.last().expect("No current scope found"))];
|
||||||
&scope.values.remove(&name.as_str())
|
&scope.values.remove(&name.as_str())
|
||||||
} {
|
} {
|
||||||
if self.bindings[*index].used.is_none() {
|
if !self.bindings[*index].used() {
|
||||||
if self.settings.rules.enabled(&Rule::UnusedVariable) {
|
if self.settings.rules.enabled(&Rule::UnusedVariable) {
|
||||||
let mut diagnostic = Diagnostic::new(
|
let mut diagnostic = Diagnostic::new(
|
||||||
violations::UnusedVariable(name.to_string()),
|
violations::UnusedVariable(name.to_string()),
|
||||||
|
@ -3532,7 +3565,9 @@ where
|
||||||
&arg.node.arg,
|
&arg.node.arg,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Argument,
|
kind: BindingKind::Argument,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(arg),
|
range: Range::from_located(arg),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -3631,7 +3666,9 @@ impl<'a> Checker<'a> {
|
||||||
self.bindings.push(Binding {
|
self.bindings.push(Binding {
|
||||||
kind: BindingKind::Builtin,
|
kind: BindingKind::Builtin,
|
||||||
range: Range::default(),
|
range: Range::default(),
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
source: None,
|
source: None,
|
||||||
});
|
});
|
||||||
scope.values.insert(builtin, index);
|
scope.values.insert(builtin, index);
|
||||||
|
@ -3716,7 +3753,7 @@ impl<'a> Checker<'a> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else if in_current_scope {
|
} else if in_current_scope {
|
||||||
if existing.used.is_none()
|
if !existing.used()
|
||||||
&& binding.redefines(existing)
|
&& binding.redefines(existing)
|
||||||
&& (!self.settings.dummy_variable_rgx.is_match(name) || existing_is_import)
|
&& (!self.settings.dummy_variable_rgx.is_match(name) || existing_is_import)
|
||||||
&& !(matches!(existing.kind, BindingKind::FunctionDefinition)
|
&& !(matches!(existing.kind, BindingKind::FunctionDefinition)
|
||||||
|
@ -3749,7 +3786,9 @@ impl<'a> Checker<'a> {
|
||||||
let binding = match scope.values.get(&name) {
|
let binding = match scope.values.get(&name) {
|
||||||
None => binding,
|
None => binding,
|
||||||
Some(index) => Binding {
|
Some(index) => Binding {
|
||||||
used: self.bindings[*index].used,
|
runtime_usage: self.bindings[*index].runtime_usage,
|
||||||
|
synthetic_usage: self.bindings[*index].synthetic_usage,
|
||||||
|
typing_usage: self.bindings[*index].typing_usage,
|
||||||
..binding
|
..binding
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -3784,8 +3823,14 @@ impl<'a> Checker<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(index) = scope.values.get(&id.as_str()) {
|
if let Some(index) = scope.values.get(&id.as_str()) {
|
||||||
|
let context = if self.in_type_definition || self.in_type_checking_block {
|
||||||
|
UsageContext::Typing
|
||||||
|
} else {
|
||||||
|
UsageContext::Runtime
|
||||||
|
};
|
||||||
|
|
||||||
// Mark the binding as used.
|
// Mark the binding as used.
|
||||||
self.bindings[*index].used = Some((scope_id, Range::from_located(expr)));
|
self.bindings[*index].mark_used(scope_id, Range::from_located(expr), context);
|
||||||
|
|
||||||
if matches!(self.bindings[*index].kind, BindingKind::Annotation)
|
if matches!(self.bindings[*index].kind, BindingKind::Annotation)
|
||||||
&& !self.in_deferred_string_type_definition
|
&& !self.in_deferred_string_type_definition
|
||||||
|
@ -3813,8 +3858,11 @@ impl<'a> Checker<'a> {
|
||||||
if has_alias {
|
if has_alias {
|
||||||
// Mark the sub-importation as used.
|
// Mark the sub-importation as used.
|
||||||
if let Some(index) = scope.values.get(full_name) {
|
if let Some(index) = scope.values.get(full_name) {
|
||||||
self.bindings[*index].used =
|
self.bindings[*index].mark_used(
|
||||||
Some((scope_id, Range::from_located(expr)));
|
scope_id,
|
||||||
|
Range::from_located(expr),
|
||||||
|
context,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3827,8 +3875,11 @@ impl<'a> Checker<'a> {
|
||||||
if has_alias {
|
if has_alias {
|
||||||
// Mark the sub-importation as used.
|
// Mark the sub-importation as used.
|
||||||
if let Some(index) = scope.values.get(full_name.as_str()) {
|
if let Some(index) = scope.values.get(full_name.as_str()) {
|
||||||
self.bindings[*index].used =
|
self.bindings[*index].mark_used(
|
||||||
Some((scope_id, Range::from_located(expr)));
|
scope_id,
|
||||||
|
Range::from_located(expr),
|
||||||
|
context,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3956,7 +4007,9 @@ impl<'a> Checker<'a> {
|
||||||
id,
|
id,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Annotation,
|
kind: BindingKind::Annotation,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(expr),
|
range: Range::from_located(expr),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -3973,7 +4026,9 @@ impl<'a> Checker<'a> {
|
||||||
id,
|
id,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::LoopVar,
|
kind: BindingKind::LoopVar,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(expr),
|
range: Range::from_located(expr),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -3986,7 +4041,9 @@ impl<'a> Checker<'a> {
|
||||||
id,
|
id,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Binding,
|
kind: BindingKind::Binding,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(expr),
|
range: Range::from_located(expr),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -4036,7 +4093,9 @@ impl<'a> Checker<'a> {
|
||||||
current,
|
current,
|
||||||
&self.bindings,
|
&self.bindings,
|
||||||
)),
|
)),
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(expr),
|
range: Range::from_located(expr),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -4049,7 +4108,9 @@ impl<'a> Checker<'a> {
|
||||||
id,
|
id,
|
||||||
Binding {
|
Binding {
|
||||||
kind: BindingKind::Assignment,
|
kind: BindingKind::Assignment,
|
||||||
used: None,
|
runtime_usage: None,
|
||||||
|
synthetic_usage: None,
|
||||||
|
typing_usage: None,
|
||||||
range: Range::from_located(expr),
|
range: Range::from_located(expr),
|
||||||
source: Some(self.current_stmt().clone()),
|
source: Some(self.current_stmt().clone()),
|
||||||
},
|
},
|
||||||
|
@ -4237,6 +4298,10 @@ impl<'a> Checker<'a> {
|
||||||
.settings
|
.settings
|
||||||
.rules
|
.rules
|
||||||
.enabled(&Rule::GlobalVariableNotAssigned)
|
.enabled(&Rule::GlobalVariableNotAssigned)
|
||||||
|
&& !self
|
||||||
|
.settings
|
||||||
|
.rules
|
||||||
|
.enabled(&Rule::RuntimeImportInTypeCheckingBlock)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -4313,7 +4378,7 @@ impl<'a> Checker<'a> {
|
||||||
| BindingKind::FutureImportation
|
| BindingKind::FutureImportation
|
||||||
) {
|
) {
|
||||||
// Skip used exports from `__all__`
|
// Skip used exports from `__all__`
|
||||||
if binding.used.is_some()
|
if binding.used()
|
||||||
|| all_names
|
|| all_names
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|names| names.contains(name))
|
.map(|names| names.contains(name))
|
||||||
|
@ -4369,6 +4434,24 @@ impl<'a> Checker<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TYP001
|
||||||
|
for (_name, index) in scope
|
||||||
|
.values
|
||||||
|
.iter()
|
||||||
|
.chain(scope.overridden.iter().map(|(a, b)| (a, b)))
|
||||||
|
{
|
||||||
|
let binding = &self.bindings[*index];
|
||||||
|
|
||||||
|
if let Some(diagnostic) =
|
||||||
|
flake8_type_checking::rules::runtime_import_in_type_checking_block(
|
||||||
|
binding,
|
||||||
|
&self.type_checking_blocks,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if self.settings.rules.enabled(&Rule::UnusedImport) {
|
if self.settings.rules.enabled(&Rule::UnusedImport) {
|
||||||
// Collect all unused imports by location. (Multiple unused imports at the same
|
// Collect all unused imports by location. (Multiple unused imports at the same
|
||||||
// location indicates an `import from`.)
|
// location indicates an `import from`.)
|
||||||
|
@ -4395,7 +4478,7 @@ impl<'a> Checker<'a> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Skip used exports from `__all__`
|
// Skip used exports from `__all__`
|
||||||
if binding.used.is_some()
|
if binding.used()
|
||||||
|| all_names
|
|| all_names
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|names| names.contains(name))
|
.map(|names| names.contains(name))
|
||||||
|
|
|
@ -429,6 +429,7 @@ ruff_macros::define_rule_mapping!(
|
||||||
EXE004 => rules::flake8_executable::rules::ShebangWhitespace,
|
EXE004 => rules::flake8_executable::rules::ShebangWhitespace,
|
||||||
EXE005 => rules::flake8_executable::rules::ShebangNewline,
|
EXE005 => rules::flake8_executable::rules::ShebangNewline,
|
||||||
// flake8-type-checking
|
// flake8-type-checking
|
||||||
|
TYP004 => rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock,
|
||||||
TYP005 => rules::flake8_type_checking::rules::EmptyTypeCheckingBlock,
|
TYP005 => rules::flake8_type_checking::rules::EmptyTypeCheckingBlock,
|
||||||
// tryceratops
|
// tryceratops
|
||||||
TRY004 => rules::tryceratops::rules::PreferTypeError,
|
TRY004 => rules::tryceratops::rules::PreferTypeError,
|
||||||
|
|
9
src/rules/flake8_type_checking/helpers.rs
Normal file
9
src/rules/flake8_type_checking/helpers.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
use rustpython_ast::Expr;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
|
||||||
|
pub fn is_type_checking_block(checker: &Checker, test: &Expr) -> bool {
|
||||||
|
checker.resolve_call_path(test).map_or(false, |call_path| {
|
||||||
|
call_path.as_slice() == ["typing", "TYPE_CHECKING"]
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
//! Rules from [flake8-type-checking](https://pypi.org/project/flake8-type-checking/).
|
//! Rules from [flake8-type-checking](https://pypi.org/project/flake8-type-checking/).
|
||||||
|
pub(crate) mod helpers;
|
||||||
pub(crate) mod rules;
|
pub(crate) mod rules;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -13,6 +14,14 @@ mod tests {
|
||||||
use crate::registry::Rule;
|
use crate::registry::Rule;
|
||||||
use crate::settings;
|
use crate::settings;
|
||||||
|
|
||||||
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TYP004_1.py"); "TYP004_1")]
|
||||||
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TYP004_2.py"); "TYP004_2")]
|
||||||
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TYP004_3.py"); "TYP004_3")]
|
||||||
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TYP004_4.py"); "TYP004_4")]
|
||||||
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TYP004_5.py"); "TYP004_5")]
|
||||||
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TYP004_6.py"); "TYP004_6")]
|
||||||
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TYP004_7.py"); "TYP004_7")]
|
||||||
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("TYP004_8.py"); "TYP004_8")]
|
||||||
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TYP005.py"); "TYP005")]
|
#[test_case(Rule::EmptyTypeCheckingBlock, Path::new("TYP005.py"); "TYP005")]
|
||||||
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
|
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
pub use empty_type_checking_block::{empty_type_checking_block, EmptyTypeCheckingBlock};
|
pub use empty_type_checking_block::{empty_type_checking_block, EmptyTypeCheckingBlock};
|
||||||
|
pub use runtime_import_in_type_checking_block::{
|
||||||
|
runtime_import_in_type_checking_block, RuntimeImportInTypeCheckingBlock,
|
||||||
|
};
|
||||||
|
|
||||||
mod empty_type_checking_block;
|
mod empty_type_checking_block;
|
||||||
|
mod runtime_import_in_type_checking_block;
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
use ruff_macros::derive_message_formats;
|
||||||
|
use rustpython_ast::Stmt;
|
||||||
|
|
||||||
|
use crate::ast::types::{Binding, BindingKind, Range};
|
||||||
|
use crate::define_violation;
|
||||||
|
use crate::registry::Diagnostic;
|
||||||
|
use crate::violation::Violation;
|
||||||
|
|
||||||
|
define_violation!(
|
||||||
|
pub struct RuntimeImportInTypeCheckingBlock {
|
||||||
|
pub full_name: String,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
impl Violation for RuntimeImportInTypeCheckingBlock {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Move import `{}` out of type-checking block. Import is used for more than type \
|
||||||
|
hinting.",
|
||||||
|
self.full_name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TYP004
|
||||||
|
pub fn runtime_import_in_type_checking_block(
|
||||||
|
binding: &Binding,
|
||||||
|
blocks: &[&Stmt],
|
||||||
|
) -> Option<Diagnostic> {
|
||||||
|
let full_name = match &binding.kind {
|
||||||
|
BindingKind::Importation(.., full_name) => full_name,
|
||||||
|
BindingKind::FromImportation(.., full_name) => full_name.as_str(),
|
||||||
|
BindingKind::SubmoduleImportation(.., full_name) => full_name,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let defined_in_type_checking = blocks
|
||||||
|
.iter()
|
||||||
|
.any(|block| Range::from_located(block).contains(&binding.range));
|
||||||
|
if defined_in_type_checking {
|
||||||
|
if binding.runtime_usage.is_some() {
|
||||||
|
return Some(Diagnostic::new(
|
||||||
|
RuntimeImportInTypeCheckingBlock {
|
||||||
|
full_name: full_name.to_string(),
|
||||||
|
},
|
||||||
|
binding.range,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_type_checking/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
RuntimeImportInTypeCheckingBlock:
|
||||||
|
full_name: datetime.datetime
|
||||||
|
location:
|
||||||
|
row: 4
|
||||||
|
column: 25
|
||||||
|
end_location:
|
||||||
|
row: 4
|
||||||
|
column: 33
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_type_checking/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
- kind:
|
||||||
|
RuntimeImportInTypeCheckingBlock:
|
||||||
|
full_name: datetime.date
|
||||||
|
location:
|
||||||
|
row: 4
|
||||||
|
column: 25
|
||||||
|
end_location:
|
||||||
|
row: 4
|
||||||
|
column: 29
|
||||||
|
fix: ~
|
||||||
|
parent: ~
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_type_checking/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_type_checking/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_type_checking/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_type_checking/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_type_checking/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
source: src/rules/flake8_type_checking/mod.rs
|
||||||
|
expression: diagnostics
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
|
|
@ -43,7 +43,7 @@ fn function(
|
||||||
.get(&arg.node.arg.as_str())
|
.get(&arg.node.arg.as_str())
|
||||||
.map(|index| &bindings[*index])
|
.map(|index| &bindings[*index])
|
||||||
{
|
{
|
||||||
if binding.used.is_none()
|
if !binding.used()
|
||||||
&& matches!(binding.kind, BindingKind::Argument)
|
&& matches!(binding.kind, BindingKind::Argument)
|
||||||
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
|
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
|
||||||
{
|
{
|
||||||
|
@ -88,7 +88,7 @@ fn method(
|
||||||
.get(&arg.node.arg.as_str())
|
.get(&arg.node.arg.as_str())
|
||||||
.map(|index| &bindings[*index])
|
.map(|index| &bindings[*index])
|
||||||
{
|
{
|
||||||
if binding.used.is_none()
|
if !binding.used()
|
||||||
&& matches!(binding.kind, BindingKind::Argument)
|
&& matches!(binding.kind, BindingKind::Argument)
|
||||||
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
|
&& !dummy_variable_rgx.is_match(arg.node.arg.as_str())
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,7 +44,7 @@ pub fn undefined_local(name: &str, scopes: &[&Scope], bindings: &[Binding]) -> O
|
||||||
for scope in scopes.iter().rev().skip(1) {
|
for scope in scopes.iter().rev().skip(1) {
|
||||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
||||||
if let Some(binding) = scope.values.get(name).map(|index| &bindings[*index]) {
|
if let Some(binding) = scope.values.get(name).map(|index| &bindings[*index]) {
|
||||||
if let Some((scope_id, location)) = binding.used {
|
if let Some((scope_id, location)) = binding.runtime_usage {
|
||||||
if scope_id == current.id {
|
if scope_id == current.id {
|
||||||
return Some(Diagnostic::new(
|
return Some(Diagnostic::new(
|
||||||
violations::UndefinedLocal(name.to_string()),
|
violations::UndefinedLocal(name.to_string()),
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub fn unused_annotation(checker: &mut Checker, scope: usize) {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, index)| (name, &checker.bindings[*index]))
|
.map(|(name, index)| (name, &checker.bindings[*index]))
|
||||||
{
|
{
|
||||||
if binding.used.is_none()
|
if !binding.used()
|
||||||
&& matches!(binding.kind, BindingKind::Annotation)
|
&& matches!(binding.kind, BindingKind::Annotation)
|
||||||
&& !checker.settings.dummy_variable_rgx.is_match(name)
|
&& !checker.settings.dummy_variable_rgx.is_match(name)
|
||||||
{
|
{
|
||||||
|
|
|
@ -156,7 +156,7 @@ pub fn unused_variable(checker: &mut Checker, scope: usize) {
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, index)| (name, &checker.bindings[*index]))
|
.map(|(name, index)| (name, &checker.bindings[*index]))
|
||||||
{
|
{
|
||||||
if binding.used.is_none()
|
if !binding.used()
|
||||||
&& matches!(binding.kind, BindingKind::Assignment)
|
&& matches!(binding.kind, BindingKind::Assignment)
|
||||||
&& !checker.settings.dummy_variable_rgx.is_match(name)
|
&& !checker.settings.dummy_variable_rgx.is_match(name)
|
||||||
&& name != &"__tracebackhide__"
|
&& name != &"__tracebackhide__"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue