[flake8-pyi] Implement PYI057 (#11486)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Tomas R 2024-05-29 12:04:36 +02:00 committed by GitHub
parent 163c374242
commit 7659114eb3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 215 additions and 0 deletions

View file

@ -0,0 +1,10 @@
import typing
import collections.abc
import foo
from typing import ByteString
from collections.abc import ByteString
from foo import ByteString
a: typing.ByteString
b: collections.abc.ByteString
c: foo.ByteString

View file

@ -0,0 +1,10 @@
import typing
import collections.abc
import foo
from typing import ByteString
from collections.abc import ByteString
from foo import ByteString
a: typing.ByteString
b: collections.abc.ByteString
c: foo.ByteString

View file

@ -346,6 +346,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
if checker.enabled(Rule::PandasUseOfDotValues) { if checker.enabled(Rule::PandasUseOfDotValues) {
pandas_vet::rules::attr(checker, attribute); pandas_vet::rules::attr(checker, attribute);
} }
if checker.enabled(Rule::ByteStringUsage) {
flake8_pyi::rules::bytestring_attribute(checker, expr);
}
} }
Expr::Call( Expr::Call(
call @ ast::ExprCall { call @ ast::ExprCall {

View file

@ -1027,6 +1027,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
checker.diagnostics.push(diagnostic); checker.diagnostics.push(diagnostic);
} }
} }
if checker.enabled(Rule::ByteStringUsage) {
flake8_pyi::rules::bytestring_import(checker, import_from);
}
} }
Stmt::Raise(raise @ ast::StmtRaise { exc, .. }) => { Stmt::Raise(raise @ ast::StmtRaise { exc, .. }) => {
if checker.enabled(Rule::RaiseNotImplemented) { if checker.enabled(Rule::RaiseNotImplemented) {

View file

@ -809,6 +809,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Flake8Pyi, "055") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnnecessaryTypeUnion), (Flake8Pyi, "055") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnnecessaryTypeUnion),
(Flake8Pyi, "056") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll), (Flake8Pyi, "056") => (RuleGroup::Stable, rules::flake8_pyi::rules::UnsupportedMethodCallOnAll),
(Flake8Pyi, "058") => (RuleGroup::Stable, rules::flake8_pyi::rules::GeneratorReturnFromIterMethod), (Flake8Pyi, "058") => (RuleGroup::Stable, rules::flake8_pyi::rules::GeneratorReturnFromIterMethod),
(Flake8Pyi, "057") => (RuleGroup::Preview, rules::flake8_pyi::rules::ByteStringUsage),
(Flake8Pyi, "059") => (RuleGroup::Preview, rules::flake8_pyi::rules::GenericNotLastBaseClass), (Flake8Pyi, "059") => (RuleGroup::Preview, rules::flake8_pyi::rules::GenericNotLastBaseClass),
(Flake8Pyi, "062") => (RuleGroup::Preview, rules::flake8_pyi::rules::DuplicateLiteralMember), (Flake8Pyi, "062") => (RuleGroup::Preview, rules::flake8_pyi::rules::DuplicateLiteralMember),
(Flake8Pyi, "064") => (RuleGroup::Preview, rules::flake8_pyi::rules::RedundantFinalLiteral), (Flake8Pyi, "064") => (RuleGroup::Preview, rules::flake8_pyi::rules::RedundantFinalLiteral),

View file

@ -25,6 +25,8 @@ mod tests {
#[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))] #[test_case(Rule::BadVersionInfoComparison, Path::new("PYI006.pyi"))]
#[test_case(Rule::BadVersionInfoOrder, Path::new("PYI066.py"))] #[test_case(Rule::BadVersionInfoOrder, Path::new("PYI066.py"))]
#[test_case(Rule::BadVersionInfoOrder, Path::new("PYI066.pyi"))] #[test_case(Rule::BadVersionInfoOrder, Path::new("PYI066.pyi"))]
#[test_case(Rule::ByteStringUsage, Path::new("PYI057.py"))]
#[test_case(Rule::ByteStringUsage, Path::new("PYI057.pyi"))]
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.py"))] #[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.py"))]
#[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.pyi"))] #[test_case(Rule::CollectionsNamedTuple, Path::new("PYI024.pyi"))]
#[test_case(Rule::ComplexAssignmentInStub, Path::new("PYI017.py"))] #[test_case(Rule::ComplexAssignmentInStub, Path::new("PYI017.py"))]

View file

@ -0,0 +1,105 @@
use ruff_diagnostics::{Diagnostic, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast::{self as ast, Expr};
use ruff_python_semantic::Modules;
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for uses of `typing.ByteString` or `collections.abc.ByteString`.
///
/// ## Why is this bad?
/// `ByteString` has been deprecated since Python 3.9 and will be removed in
/// Python 3.14. The Python documentation recommends using either
/// `collections.abc.Buffer` (or the `typing_extensions` backport
/// on Python <3.12) or a union like `bytes | bytearray | memoryview` instead.
///
/// ## Example
/// ```python
/// from typing import ByteString
/// ```
///
/// Use instead:
/// ```python
/// from collections.abc import Buffer
/// ```
///
/// ## References
/// - [Python documentation: The `ByteString` type](https://docs.python.org/3/library/typing.html#typing.ByteString)
#[violation]
pub struct ByteStringUsage {
origin: ByteStringOrigin,
}
impl Violation for ByteStringUsage {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::None;
#[derive_message_formats]
fn message(&self) -> String {
let ByteStringUsage { origin } = self;
format!("Do not use `{origin}.ByteString`, which has unclear semantics and is deprecated")
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
enum ByteStringOrigin {
Typing,
CollectionsAbc,
}
impl std::fmt::Display for ByteStringOrigin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Typing => "typing",
Self::CollectionsAbc => "collections.abc",
})
}
}
/// PYI057
pub(crate) fn bytestring_attribute(checker: &mut Checker, attribute: &Expr) {
let semantic = checker.semantic();
if !semantic
.seen
.intersects(Modules::TYPING | Modules::COLLECTIONS)
{
return;
}
let Some(qualified_name) = semantic.resolve_qualified_name(attribute) else {
return;
};
let origin = match qualified_name.segments() {
["typing", "ByteString"] => ByteStringOrigin::Typing,
["collections", "abc", "ByteString"] => ByteStringOrigin::CollectionsAbc,
_ => return,
};
checker.diagnostics.push(Diagnostic::new(
ByteStringUsage { origin },
attribute.range(),
));
}
/// PYI057
pub(crate) fn bytestring_import(checker: &mut Checker, import_from: &ast::StmtImportFrom) {
let ast::StmtImportFrom { names, module, .. } = import_from;
let module_id = match module {
Some(module) => module.id.as_str(),
None => return,
};
let origin = match module_id {
"typing" => ByteStringOrigin::Typing,
"collections.abc" => ByteStringOrigin::CollectionsAbc,
_ => return,
};
for name in names {
if name.name.as_str() == "ByteString" {
checker
.diagnostics
.push(Diagnostic::new(ByteStringUsage { origin }, name.range()));
}
}
}

View file

@ -1,6 +1,7 @@
pub(crate) use any_eq_ne_annotation::*; pub(crate) use any_eq_ne_annotation::*;
pub(crate) use bad_generator_return_type::*; pub(crate) use bad_generator_return_type::*;
pub(crate) use bad_version_info_comparison::*; pub(crate) use bad_version_info_comparison::*;
pub(crate) use bytestring_usage::*;
pub(crate) use collections_named_tuple::*; pub(crate) use collections_named_tuple::*;
pub(crate) use complex_assignment_in_stub::*; pub(crate) use complex_assignment_in_stub::*;
pub(crate) use complex_if_statement_in_stub::*; pub(crate) use complex_if_statement_in_stub::*;
@ -42,6 +43,7 @@ pub(crate) use unused_private_type_definition::*;
mod any_eq_ne_annotation; mod any_eq_ne_annotation;
mod bad_generator_return_type; mod bad_generator_return_type;
mod bad_version_info_comparison; mod bad_version_info_comparison;
mod bytestring_usage;
mod collections_named_tuple; mod collections_named_tuple;
mod complex_assignment_in_stub; mod complex_assignment_in_stub;
mod complex_if_statement_in_stub; mod complex_if_statement_in_stub;

View file

@ -0,0 +1,39 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI057.py:4:20: PYI057 Do not use `typing.ByteString`, which has unclear semantics and is deprecated
|
2 | import collections.abc
3 | import foo
4 | from typing import ByteString
| ^^^^^^^^^^ PYI057
5 | from collections.abc import ByteString
6 | from foo import ByteString
|
PYI057.py:5:29: PYI057 Do not use `collections.abc.ByteString`, which has unclear semantics and is deprecated
|
3 | import foo
4 | from typing import ByteString
5 | from collections.abc import ByteString
| ^^^^^^^^^^ PYI057
6 | from foo import ByteString
|
PYI057.py:8:4: PYI057 Do not use `typing.ByteString`, which has unclear semantics and is deprecated
|
6 | from foo import ByteString
7 |
8 | a: typing.ByteString
| ^^^^^^^^^^^^^^^^^ PYI057
9 | b: collections.abc.ByteString
10 | c: foo.ByteString
|
PYI057.py:9:4: PYI057 Do not use `collections.abc.ByteString`, which has unclear semantics and is deprecated
|
8 | a: typing.ByteString
9 | b: collections.abc.ByteString
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI057
10 | c: foo.ByteString
|

View file

@ -0,0 +1,39 @@
---
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs
---
PYI057.pyi:4:20: PYI057 Do not use `typing.ByteString`, which has unclear semantics and is deprecated
|
2 | import collections.abc
3 | import foo
4 | from typing import ByteString
| ^^^^^^^^^^ PYI057
5 | from collections.abc import ByteString
6 | from foo import ByteString
|
PYI057.pyi:5:29: PYI057 Do not use `collections.abc.ByteString`, which has unclear semantics and is deprecated
|
3 | import foo
4 | from typing import ByteString
5 | from collections.abc import ByteString
| ^^^^^^^^^^ PYI057
6 | from foo import ByteString
|
PYI057.pyi:8:4: PYI057 Do not use `typing.ByteString`, which has unclear semantics and is deprecated
|
6 | from foo import ByteString
7 |
8 | a: typing.ByteString
| ^^^^^^^^^^^^^^^^^ PYI057
9 | b: collections.abc.ByteString
10 | c: foo.ByteString
|
PYI057.pyi:9:4: PYI057 Do not use `collections.abc.ByteString`, which has unclear semantics and is deprecated
|
8 | a: typing.ByteString
9 | b: collections.abc.ByteString
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ PYI057
10 | c: foo.ByteString
|

1
ruff.schema.json generated
View file

@ -3576,6 +3576,7 @@
"PYI054", "PYI054",
"PYI055", "PYI055",
"PYI056", "PYI056",
"PYI057",
"PYI058", "PYI058",
"PYI059", "PYI059",
"PYI06", "PYI06",