Add PYI024 for flake8-pyi plugin (#4756)

This commit is contained in:
qdegraaf 2023-05-31 18:07:04 +02:00 committed by GitHub
parent 9d0ffd33ca
commit 2b2812c4f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 142 additions and 0 deletions

View file

@ -0,0 +1,9 @@
import collections
person: collections.namedtuple # OK
from collections import namedtuple
person: namedtuple # OK
person = namedtuple("Person", ["name", "age"]) # OK

View file

@ -0,0 +1,11 @@
import collections
person: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
from collections import namedtuple
person: namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
person = namedtuple(
"Person", ["name", "age"]
) # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"

View file

@ -2292,6 +2292,11 @@ where
if self.enabled(Rule::NumpyDeprecatedTypeAlias) {
numpy::rules::deprecated_type_alias(self, expr);
}
if self.is_stub {
if self.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(self, expr);
}
}
// Ex) List[...]
if self.any_enabled(&[
@ -2424,6 +2429,11 @@ where
if self.enabled(Rule::PrivateMemberAccess) {
flake8_self::rules::private_member_access(self, expr);
}
if self.is_stub {
if self.enabled(Rule::CollectionsNamedTuple) {
flake8_pyi::rules::collections_named_tuple(self, expr);
}
}
pandas_vet::rules::attr(self, attr, value, expr);
}
Expr::Call(ast::ExprCall {

View file

@ -591,6 +591,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "016") => (RuleGroup::Unspecified, Rule::DuplicateUnionMember),
(Flake8Pyi, "020") => (RuleGroup::Unspecified, Rule::QuotedAnnotationInStub),
(Flake8Pyi, "021") => (RuleGroup::Unspecified, Rule::DocstringInStub),
(Flake8Pyi, "024") => (RuleGroup::Unspecified, Rule::CollectionsNamedTuple),
(Flake8Pyi, "032") => (RuleGroup::Unspecified, Rule::AnyEqNeAnnotation),
(Flake8Pyi, "033") => (RuleGroup::Unspecified, Rule::TypeCommentInStub),
(Flake8Pyi, "042") => (RuleGroup::Unspecified, Rule::SnakeCaseTypeAlias),

View file

@ -518,6 +518,7 @@ ruff_macros::register_rules!(
rules::flake8_pyi::rules::IterMethodReturnIterable,
rules::flake8_pyi::rules::DuplicateUnionMember,
rules::flake8_pyi::rules::EllipsisInNonEmptyClassBody,
rules::flake8_pyi::rules::CollectionsNamedTuple,
rules::flake8_pyi::rules::NonEmptyStubBody,
rules::flake8_pyi::rules::PassInClassBody,
rules::flake8_pyi::rules::PassStatementStubBody,

View file

@ -26,6 +26,8 @@ mod tests {
#[test_case(Rule::DuplicateUnionMember, Path::new("PYI016.pyi"))]
#[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.py"))]
#[test_case(Rule::EllipsisInNonEmptyClassBody, Path::new("PYI013.pyi"))]
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.py"))]
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.pyi"))]
#[test_case(Rule::IterMethodReturnIterable, Path::new("PYI045.py"))]
#[test_case(Rule::IterMethodReturnIterable, Path::new("PYI045.pyi"))]
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.py"))]

View file

@ -0,0 +1,64 @@
use rustpython_parser::ast::Expr;
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::prelude::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for uses of `collections.namedtuple` in stub files.
///
/// ## Why is this bad?
/// `typing.NamedTuple` is the "typed version" of `collections.namedtuple`.
///
/// The class generated by subclassing `typing.NamedTuple` is equivalent to
/// `collections.namedtuple`, with the exception that `typing.NamedTuple`
/// includes an `__annotations__` attribute, which allows type checkers to
/// infer the types of the fields.
///
/// ## Example
/// ```python
/// from collections import namedtuple
///
///
/// person = namedtuple("Person", ["name", "age"])
/// ```
///
/// Use instead:
/// ```python
/// from typing import NamedTuple
///
///
/// class Person(NamedTuple):
/// name: str
/// age: int
/// ```
#[violation]
pub struct CollectionsNamedTuple;
impl Violation for CollectionsNamedTuple {
#[derive_message_formats]
fn message(&self) -> String {
format!("Use `typing.NamedTuple` instead of `collections.namedtuple`")
}
fn autofix_title(&self) -> Option<String> {
Some(format!("Replace with `typing.NamedTuple`"))
}
}
/// PYI024
pub(crate) fn collections_named_tuple(checker: &mut Checker, expr: &Expr) {
if checker
.semantic_model()
.resolve_call_path(expr)
.map_or(false, |call_path| {
matches!(call_path.as_slice(), ["collections", "namedtuple"])
})
{
checker
.diagnostics
.push(Diagnostic::new(CollectionsNamedTuple, expr.range()));
}
}

View file

@ -2,6 +2,7 @@ pub(crate) use any_eq_ne_annotation::{any_eq_ne_annotation, AnyEqNeAnnotation};
pub(crate) use bad_version_info_comparison::{
bad_version_info_comparison, BadVersionInfoComparison,
};
pub(crate) use collections_named_tuple::{collections_named_tuple, CollectionsNamedTuple};
pub(crate) use docstring_in_stubs::{docstring_in_stubs, DocstringInStub};
pub(crate) use duplicate_union_member::{duplicate_union_member, DuplicateUnionMember};
pub(crate) use ellipsis_in_non_empty_class_body::{
@ -33,6 +34,7 @@ pub(crate) use unrecognized_platform::{
mod any_eq_ne_annotation;
mod bad_version_info_comparison;
mod collections_named_tuple;
mod docstring_in_stubs;
mod duplicate_union_member;
mod ellipsis_in_non_empty_class_body;

View file

@ -0,0 +1,4 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---

View file

@ -0,0 +1,37 @@
---
source: crates/ruff/src/rules/flake8_pyi/mod.rs
---
PYI024.pyi:3:9: PYI024 Use `typing.NamedTuple` instead of `collections.namedtuple`
|
3 | import collections
4 |
5 | person: collections.namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
| ^^^^^^^^^^^^^^^^^^^^^^ PYI024
6 |
7 | from collections import namedtuple
|
= help: Replace with `typing.NamedTuple`
PYI024.pyi:7:9: PYI024 Use `typing.NamedTuple` instead of `collections.namedtuple`
|
7 | from collections import namedtuple
8 |
9 | person: namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
| ^^^^^^^^^^ PYI024
10 |
11 | person = namedtuple(
|
= help: Replace with `typing.NamedTuple`
PYI024.pyi:9:10: PYI024 Use `typing.NamedTuple` instead of `collections.namedtuple`
|
9 | person: namedtuple # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
10 |
11 | person = namedtuple(
| ^^^^^^^^^^ PYI024
12 | "Person", ["name", "age"]
13 | ) # Y024 Use "typing.NamedTuple" instead of "collections.namedtuple"
|
= help: Replace with `typing.NamedTuple`

1
ruff.schema.json generated
View file

@ -2209,6 +2209,7 @@
"PYI02",
"PYI020",
"PYI021",
"PYI024",
"PYI03",
"PYI032",
"PYI033",