[flake8-pyi] Add autofix for unused-private-type-var (PYI018) (#15999)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Ayush Baweja 2025-02-06 13:08:36 -05:00 committed by GitHub
parent 18b497a913
commit ba2f0e998d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 248 additions and 13 deletions

View File

@ -2218,13 +2218,11 @@ def func(t: _T) -> _T:
return x
"#
),
@r#"
success: false
exit_code: 1
@r"
success: true
exit_code: 0
----- stdout -----
from typing import TypeVar
_T = TypeVar("_T")
class OldStyle[T]:
var: T
@ -2234,8 +2232,7 @@ def func(t: _T) -> _T:
return x
----- stderr -----
test.py:3:1: PYI018 Private TypeVar `_T` is never used
Found 6 errors (5 fixed, 1 remaining).
"#
Found 7 errors (7 fixed, 0 remaining).
"
);
}

View File

@ -195,6 +195,8 @@ mod tests {
}
#[test_case(Rule::FutureAnnotationsInStub, Path::new("PYI044.pyi"))]
#[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.py"))]
#[test_case(Rule::UnusedPrivateTypeVar, Path::new("PYI018.pyi"))]
fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!(
"preview__{}_{}",

View File

@ -1,4 +1,4 @@
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::map_subscript;
use ruff_python_ast::{self as ast, Expr, Stmt};
@ -6,6 +6,7 @@ use ruff_python_semantic::{Scope, SemanticModel};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
use crate::fix;
/// ## What it does
/// Checks for the presence of unused private `TypeVar`, `ParamSpec` or
@ -13,7 +14,8 @@ use crate::checkers::ast::Checker;
///
/// ## Why is this bad?
/// A private `TypeVar` that is defined but not used is likely a mistake. It
/// should either be used, made public, or removed to avoid confusion.
/// should either be used, made public, or removed to avoid confusion. A type
/// variable is considered "private" if its name starts with an underscore.
///
/// ## Example
/// ```pyi
@ -23,6 +25,11 @@ use crate::checkers::ast::Checker;
/// _T = typing.TypeVar("_T")
/// _Ts = typing_extensions.TypeVarTuple("_Ts")
/// ```
///
/// ## Fix safety and availability
/// This rule's fix is available when [`preview`] mode is enabled.
/// It is always marked as unsafe, as it would break your code if the type
/// variable is imported by another module.
#[derive(ViolationMetadata)]
pub(crate) struct UnusedPrivateTypeVar {
type_var_like_name: String,
@ -30,6 +37,8 @@ pub(crate) struct UnusedPrivateTypeVar {
}
impl Violation for UnusedPrivateTypeVar {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
let UnusedPrivateTypeVar {
@ -38,6 +47,16 @@ impl Violation for UnusedPrivateTypeVar {
} = self;
format!("Private {type_var_like_kind} `{type_var_like_name}` is never used")
}
fn fix_title(&self) -> Option<String> {
let UnusedPrivateTypeVar {
type_var_like_name,
type_var_like_kind,
} = self;
Some(format!(
"Remove unused private {type_var_like_kind} `{type_var_like_name}`"
))
}
}
/// ## What it does
@ -178,7 +197,7 @@ pub(crate) fn unused_private_type_var(
let Some(source) = binding.source else {
continue;
};
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) =
let stmt @ Stmt::Assign(ast::StmtAssign { targets, value, .. }) =
checker.semantic().statement(source)
else {
continue;
@ -210,13 +229,20 @@ pub(crate) fn unused_private_type_var(
continue;
};
diagnostics.push(Diagnostic::new(
let mut diagnostic = Diagnostic::new(
UnusedPrivateTypeVar {
type_var_like_name: id.to_string(),
type_var_like_kind: type_var_like_kind.to_string(),
},
binding.range(),
));
);
if checker.settings.preview.is_enabled() {
let edit = fix::edits::delete_stmt(stmt, None, checker.locator(), checker.indexer());
diagnostic.set_fix(Fix::unsafe_edit(edit));
}
diagnostics.push(diagnostic);
}
}

View File

@ -10,6 +10,7 @@ PYI018.py:6:1: PYI018 Private TypeVar `_T` is never used
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 | _P = ParamSpec("_P")
|
= help: Remove unused private TypeVar `_T`
PYI018.py:7:1: PYI018 Private TypeVarTuple `_Ts` is never used
|
@ -19,6 +20,7 @@ PYI018.py:7:1: PYI018 Private TypeVarTuple `_Ts` is never used
8 | _P = ParamSpec("_P")
9 | _P2 = typing.ParamSpec("_P2")
|
= help: Remove unused private TypeVarTuple `_Ts`
PYI018.py:8:1: PYI018 Private ParamSpec `_P` is never used
|
@ -29,6 +31,7 @@ PYI018.py:8:1: PYI018 Private ParamSpec `_P` is never used
9 | _P2 = typing.ParamSpec("_P2")
10 | _Ts2 = TypeVarTuple("_Ts2")
|
= help: Remove unused private ParamSpec `_P`
PYI018.py:9:1: PYI018 Private ParamSpec `_P2` is never used
|
@ -38,6 +41,7 @@ PYI018.py:9:1: PYI018 Private ParamSpec `_P2` is never used
| ^^^ PYI018
10 | _Ts2 = TypeVarTuple("_Ts2")
|
= help: Remove unused private ParamSpec `_P2`
PYI018.py:10:1: PYI018 Private TypeVarTuple `_Ts2` is never used
|
@ -48,3 +52,4 @@ PYI018.py:10:1: PYI018 Private TypeVarTuple `_Ts2` is never used
11 |
12 | # OK
|
= help: Remove unused private TypeVarTuple `_Ts2`

View File

@ -10,6 +10,7 @@ PYI018.pyi:6:1: PYI018 Private TypeVar `_T` is never used
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 | _P = ParamSpec("_P")
|
= help: Remove unused private TypeVar `_T`
PYI018.pyi:7:1: PYI018 Private TypeVarTuple `_Ts` is never used
|
@ -19,6 +20,7 @@ PYI018.pyi:7:1: PYI018 Private TypeVarTuple `_Ts` is never used
8 | _P = ParamSpec("_P")
9 | _P2 = typing.ParamSpec("_P2")
|
= help: Remove unused private TypeVarTuple `_Ts`
PYI018.pyi:8:1: PYI018 Private ParamSpec `_P` is never used
|
@ -29,6 +31,7 @@ PYI018.pyi:8:1: PYI018 Private ParamSpec `_P` is never used
9 | _P2 = typing.ParamSpec("_P2")
10 | _Ts2 = TypeVarTuple("_Ts2")
|
= help: Remove unused private ParamSpec `_P`
PYI018.pyi:9:1: PYI018 Private ParamSpec `_P2` is never used
|
@ -38,6 +41,7 @@ PYI018.pyi:9:1: PYI018 Private ParamSpec `_P2` is never used
| ^^^ PYI018
10 | _Ts2 = TypeVarTuple("_Ts2")
|
= help: Remove unused private ParamSpec `_P2`
PYI018.pyi:10:1: PYI018 Private TypeVarTuple `_Ts2` is never used
|
@ -48,3 +52,4 @@ PYI018.pyi:10:1: PYI018 Private TypeVarTuple `_Ts2` is never used
11 |
12 | # OK
|
= help: Remove unused private TypeVarTuple `_Ts2`

View File

@ -0,0 +1,100 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI018.py:6:1: PYI018 [*] Private TypeVar `_T` is never used
|
4 | from typing_extensions import ParamSpec, TypeVarTuple
5 |
6 | _T = typing.TypeVar("_T")
| ^^ PYI018
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 | _P = ParamSpec("_P")
|
= help: Remove unused private TypeVar `_T`
Unsafe fix
3 3 | from typing import TypeVar
4 4 | from typing_extensions import ParamSpec, TypeVarTuple
5 5 |
6 |-_T = typing.TypeVar("_T")
7 6 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 7 | _P = ParamSpec("_P")
9 8 | _P2 = typing.ParamSpec("_P2")
PYI018.py:7:1: PYI018 [*] Private TypeVarTuple `_Ts` is never used
|
6 | _T = typing.TypeVar("_T")
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
| ^^^ PYI018
8 | _P = ParamSpec("_P")
9 | _P2 = typing.ParamSpec("_P2")
|
= help: Remove unused private TypeVarTuple `_Ts`
Unsafe fix
4 4 | from typing_extensions import ParamSpec, TypeVarTuple
5 5 |
6 6 | _T = typing.TypeVar("_T")
7 |-_Ts = typing_extensions.TypeVarTuple("_Ts")
8 7 | _P = ParamSpec("_P")
9 8 | _P2 = typing.ParamSpec("_P2")
10 9 | _Ts2 = TypeVarTuple("_Ts2")
PYI018.py:8:1: PYI018 [*] Private ParamSpec `_P` is never used
|
6 | _T = typing.TypeVar("_T")
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 | _P = ParamSpec("_P")
| ^^ PYI018
9 | _P2 = typing.ParamSpec("_P2")
10 | _Ts2 = TypeVarTuple("_Ts2")
|
= help: Remove unused private ParamSpec `_P`
Unsafe fix
5 5 |
6 6 | _T = typing.TypeVar("_T")
7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 |-_P = ParamSpec("_P")
9 8 | _P2 = typing.ParamSpec("_P2")
10 9 | _Ts2 = TypeVarTuple("_Ts2")
11 10 |
PYI018.py:9:1: PYI018 [*] Private ParamSpec `_P2` is never used
|
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 | _P = ParamSpec("_P")
9 | _P2 = typing.ParamSpec("_P2")
| ^^^ PYI018
10 | _Ts2 = TypeVarTuple("_Ts2")
|
= help: Remove unused private ParamSpec `_P2`
Unsafe fix
6 6 | _T = typing.TypeVar("_T")
7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 8 | _P = ParamSpec("_P")
9 |-_P2 = typing.ParamSpec("_P2")
10 9 | _Ts2 = TypeVarTuple("_Ts2")
11 10 |
12 11 | # OK
PYI018.py:10:1: PYI018 [*] Private TypeVarTuple `_Ts2` is never used
|
8 | _P = ParamSpec("_P")
9 | _P2 = typing.ParamSpec("_P2")
10 | _Ts2 = TypeVarTuple("_Ts2")
| ^^^^ PYI018
11 |
12 | # OK
|
= help: Remove unused private TypeVarTuple `_Ts2`
Unsafe fix
7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 8 | _P = ParamSpec("_P")
9 9 | _P2 = typing.ParamSpec("_P2")
10 |-_Ts2 = TypeVarTuple("_Ts2")
11 10 |
12 11 | # OK
13 12 | _UsedTypeVar = TypeVar("_UsedTypeVar")

View File

@ -0,0 +1,100 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI018.pyi:6:1: PYI018 [*] Private TypeVar `_T` is never used
|
4 | from typing_extensions import ParamSpec, TypeVarTuple
5 |
6 | _T = typing.TypeVar("_T")
| ^^ PYI018
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 | _P = ParamSpec("_P")
|
= help: Remove unused private TypeVar `_T`
Unsafe fix
3 3 | from typing import TypeVar
4 4 | from typing_extensions import ParamSpec, TypeVarTuple
5 5 |
6 |-_T = typing.TypeVar("_T")
7 6 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 7 | _P = ParamSpec("_P")
9 8 | _P2 = typing.ParamSpec("_P2")
PYI018.pyi:7:1: PYI018 [*] Private TypeVarTuple `_Ts` is never used
|
6 | _T = typing.TypeVar("_T")
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
| ^^^ PYI018
8 | _P = ParamSpec("_P")
9 | _P2 = typing.ParamSpec("_P2")
|
= help: Remove unused private TypeVarTuple `_Ts`
Unsafe fix
4 4 | from typing_extensions import ParamSpec, TypeVarTuple
5 5 |
6 6 | _T = typing.TypeVar("_T")
7 |-_Ts = typing_extensions.TypeVarTuple("_Ts")
8 7 | _P = ParamSpec("_P")
9 8 | _P2 = typing.ParamSpec("_P2")
10 9 | _Ts2 = TypeVarTuple("_Ts2")
PYI018.pyi:8:1: PYI018 [*] Private ParamSpec `_P` is never used
|
6 | _T = typing.TypeVar("_T")
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 | _P = ParamSpec("_P")
| ^^ PYI018
9 | _P2 = typing.ParamSpec("_P2")
10 | _Ts2 = TypeVarTuple("_Ts2")
|
= help: Remove unused private ParamSpec `_P`
Unsafe fix
5 5 |
6 6 | _T = typing.TypeVar("_T")
7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 |-_P = ParamSpec("_P")
9 8 | _P2 = typing.ParamSpec("_P2")
10 9 | _Ts2 = TypeVarTuple("_Ts2")
11 10 |
PYI018.pyi:9:1: PYI018 [*] Private ParamSpec `_P2` is never used
|
7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 | _P = ParamSpec("_P")
9 | _P2 = typing.ParamSpec("_P2")
| ^^^ PYI018
10 | _Ts2 = TypeVarTuple("_Ts2")
|
= help: Remove unused private ParamSpec `_P2`
Unsafe fix
6 6 | _T = typing.TypeVar("_T")
7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 8 | _P = ParamSpec("_P")
9 |-_P2 = typing.ParamSpec("_P2")
10 9 | _Ts2 = TypeVarTuple("_Ts2")
11 10 |
12 11 | # OK
PYI018.pyi:10:1: PYI018 [*] Private TypeVarTuple `_Ts2` is never used
|
8 | _P = ParamSpec("_P")
9 | _P2 = typing.ParamSpec("_P2")
10 | _Ts2 = TypeVarTuple("_Ts2")
| ^^^^ PYI018
11 |
12 | # OK
|
= help: Remove unused private TypeVarTuple `_Ts2`
Unsafe fix
7 7 | _Ts = typing_extensions.TypeVarTuple("_Ts")
8 8 | _P = ParamSpec("_P")
9 9 | _P2 = typing.ParamSpec("_P2")
10 |-_Ts2 = TypeVarTuple("_Ts2")
11 10 |
12 11 | # OK
13 12 | _UsedTypeVar = TypeVar("_UsedTypeVar")