Compare commits
7 Commits
fix-set-ch
...
PYI050
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a1591a76c | ||
|
|
ddd3ededec | ||
|
|
7cc205b5d6 | ||
|
|
e1df2b1400 | ||
|
|
fd02805fa1 | ||
|
|
0fae2f3f04 | ||
|
|
9c4ef37a77 |
1
.github/workflows/release.yaml
vendored
1
.github/workflows/release.yaml
vendored
@@ -34,6 +34,7 @@ jobs:
|
||||
args: --out dist
|
||||
- name: "Test sdist"
|
||||
run: |
|
||||
rustup default $(cat rust-toolchain)
|
||||
pip install dist/${{ env.PACKAGE_NAME }}-*.tar.gz --force-reinstall
|
||||
ruff --help
|
||||
python -m ruff --help
|
||||
|
||||
32
crates/ruff/resources/test/fixtures/flake8_pyi/PYI050.py
vendored
Normal file
32
crates/ruff/resources/test/fixtures/flake8_pyi/PYI050.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
from typing import NoReturn, Never
|
||||
import typing_extensions
|
||||
|
||||
|
||||
def foo(arg):
|
||||
...
|
||||
|
||||
|
||||
def foo_int(arg: int):
|
||||
...
|
||||
|
||||
|
||||
def foo_no_return(arg: NoReturn):
|
||||
...
|
||||
|
||||
|
||||
def foo_no_return_typing_extensions(
|
||||
arg: typing_extensions.NoReturn,
|
||||
):
|
||||
...
|
||||
|
||||
|
||||
def foo_no_return_kwarg(arg: int, *, arg2: NoReturn):
|
||||
...
|
||||
|
||||
|
||||
def foo_no_return_pos_only(arg: int, /, arg2: NoReturn):
|
||||
...
|
||||
|
||||
|
||||
def foo_never(arg: Never):
|
||||
...
|
||||
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI050.pyi
vendored
Normal file
12
crates/ruff/resources/test/fixtures/flake8_pyi/PYI050.pyi
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
from typing import NoReturn, Never
|
||||
import typing_extensions
|
||||
|
||||
def foo(arg): ...
|
||||
def foo_int(arg: int): ...
|
||||
def foo_no_return(arg: NoReturn): ... # Error: PYI050
|
||||
def foo_no_return_typing_extensions(
|
||||
arg: typing_extensions.NoReturn,
|
||||
): ... # Error: PYI050
|
||||
def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050
|
||||
def foo_no_return_pos_only(arg: int, /, arg2: NoReturn): ... # Error: PYI050
|
||||
def foo_never(arg: Never): ...
|
||||
@@ -3,12 +3,6 @@
|
||||
for item in {"apples", "lemons", "water"}: # flags in-line set literals
|
||||
print(f"I like {item}.")
|
||||
|
||||
for item in set(("apples", "lemons", "water")): # flags set() calls
|
||||
print(f"I like {item}.")
|
||||
|
||||
for number in {i for i in range(10)}: # flags set comprehensions
|
||||
print(number)
|
||||
|
||||
numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
|
||||
numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
@@ -36,3 +30,9 @@ numbers_set = {i for i in (1, 2, 3)} # tuples in comprehensions are fine
|
||||
numbers_dict = {str(i): i for i in [1, 2, 3]} # lists in dict comprehensions are fine
|
||||
|
||||
numbers_gen = (i for i in (1, 2, 3)) # tuples in generator expressions are fine
|
||||
|
||||
for item in set(("apples", "lemons", "water")): # set constructor is fine
|
||||
print(f"I like {item}.")
|
||||
|
||||
for number in {i for i in range(10)}: # set comprehensions are fine
|
||||
print(number)
|
||||
|
||||
@@ -463,6 +463,9 @@ where
|
||||
if self.enabled(Rule::StrOrReprDefinedInStub) {
|
||||
flake8_pyi::rules::str_or_repr_defined_in_stub(self, stmt);
|
||||
}
|
||||
if self.enabled(Rule::NoReturnArgumentAnnotationInStub) {
|
||||
flake8_pyi::rules::no_return_argument_annotation(self, args);
|
||||
}
|
||||
}
|
||||
|
||||
if self.enabled(Rule::DunderFunctionName) {
|
||||
|
||||
@@ -614,6 +614,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||
(Flake8Pyi, "043") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::TSuffixedTypeAlias),
|
||||
(Flake8Pyi, "045") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::IterMethodReturnIterable),
|
||||
(Flake8Pyi, "048") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StubBodyMultipleStatements),
|
||||
(Flake8Pyi, "050") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NoReturnArgumentAnnotationInStub),
|
||||
(Flake8Pyi, "052") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::UnannotatedAssignmentInStub),
|
||||
(Flake8Pyi, "054") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::NumericLiteralTooLong),
|
||||
(Flake8Pyi, "053") => (RuleGroup::Unspecified, rules::flake8_pyi::rules::StringOrBytesTooLong),
|
||||
|
||||
@@ -32,6 +32,8 @@ mod tests {
|
||||
#[test_case(Rule::NonSelfReturnType, Path::new("PYI034.pyi"))]
|
||||
#[test_case(Rule::IterMethodReturnIterable, Path::new("PYI045.py"))]
|
||||
#[test_case(Rule::IterMethodReturnIterable, Path::new("PYI045.pyi"))]
|
||||
#[test_case(Rule::NoReturnArgumentAnnotationInStub, Path::new("PYI050.py"))]
|
||||
#[test_case(Rule::NoReturnArgumentAnnotationInStub, Path::new("PYI050.pyi"))]
|
||||
#[test_case(Rule::NumericLiteralTooLong, Path::new("PYI054.py"))]
|
||||
#[test_case(Rule::NumericLiteralTooLong, Path::new("PYI054.pyi"))]
|
||||
#[test_case(Rule::NonEmptyStubBody, Path::new("PYI010.py"))]
|
||||
|
||||
@@ -11,6 +11,9 @@ pub(crate) use ellipsis_in_non_empty_class_body::{
|
||||
pub(crate) use iter_method_return_iterable::{
|
||||
iter_method_return_iterable, IterMethodReturnIterable,
|
||||
};
|
||||
pub(crate) use no_return_argument_annotation::{
|
||||
no_return_argument_annotation, NoReturnArgumentAnnotationInStub,
|
||||
};
|
||||
pub(crate) use non_empty_stub_body::{non_empty_stub_body, NonEmptyStubBody};
|
||||
pub(crate) use non_self_return_type::{non_self_return_type, NonSelfReturnType};
|
||||
pub(crate) use numeric_literal_too_long::{numeric_literal_too_long, NumericLiteralTooLong};
|
||||
@@ -47,6 +50,7 @@ mod docstring_in_stubs;
|
||||
mod duplicate_union_member;
|
||||
mod ellipsis_in_non_empty_class_body;
|
||||
mod iter_method_return_iterable;
|
||||
mod no_return_argument_annotation;
|
||||
mod non_empty_stub_body;
|
||||
mod non_self_return_type;
|
||||
mod numeric_literal_too_long;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
use std::fmt;
|
||||
|
||||
use itertools::chain;
|
||||
use rustpython_parser::ast::Ranged;
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
use ruff_python_ast::prelude::Arguments;
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::settings::types::PythonVersion::Py311;
|
||||
|
||||
#[violation]
|
||||
pub struct NoReturnArgumentAnnotationInStub {
|
||||
module: TypingModule,
|
||||
}
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for uses of `typing.NoReturn` (and `typing_extensions.NoReturn`) in
|
||||
/// stubs.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Prefer `typing.Never` (or `typing_extensions.Never`) over `typing.NoReturn`,
|
||||
/// as the former is more explicit about the intent of the annotation. This is
|
||||
/// a purely stylistic choice, as the two are semantically equivalent.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```python
|
||||
/// from typing import NoReturn
|
||||
///
|
||||
///
|
||||
/// def foo(x: NoReturn): ...
|
||||
/// ```
|
||||
///
|
||||
/// Use instead:
|
||||
/// ```python
|
||||
/// from typing import Never
|
||||
///
|
||||
///
|
||||
/// def foo(x: Never): ...
|
||||
/// ```
|
||||
impl Violation for NoReturnArgumentAnnotationInStub {
|
||||
#[derive_message_formats]
|
||||
fn message(&self) -> String {
|
||||
let NoReturnArgumentAnnotationInStub { module } = self;
|
||||
format!("Prefer `{module}.Never` over `NoReturn` for argument annotations")
|
||||
}
|
||||
}
|
||||
|
||||
/// PYI050
|
||||
pub(crate) fn no_return_argument_annotation(checker: &mut Checker, args: &Arguments) {
|
||||
for annotation in chain!(
|
||||
args.args.iter(),
|
||||
args.posonlyargs.iter(),
|
||||
args.kwonlyargs.iter()
|
||||
)
|
||||
.filter_map(|arg| arg.annotation.as_ref())
|
||||
{
|
||||
if checker
|
||||
.semantic_model()
|
||||
.match_typing_expr(annotation, "NoReturn")
|
||||
{
|
||||
checker.diagnostics.push(Diagnostic::new(
|
||||
NoReturnArgumentAnnotationInStub {
|
||||
module: if checker.settings.target_version >= Py311 {
|
||||
TypingModule::Typing
|
||||
} else {
|
||||
TypingModule::TypingExtensions
|
||||
},
|
||||
},
|
||||
annotation.range(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum TypingModule {
|
||||
Typing,
|
||||
TypingExtensions,
|
||||
}
|
||||
|
||||
impl fmt::Display for TypingModule {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
TypingModule::Typing => fmt.write_str("typing"),
|
||||
TypingModule::TypingExtensions => fmt.write_str("typing_extensions"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
---
|
||||
source: crates/ruff/src/rules/flake8_pyi/mod.rs
|
||||
---
|
||||
PYI050.pyi:6:24: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
6 | def foo(arg): ...
|
||||
7 | def foo_int(arg: int): ...
|
||||
8 | def foo_no_return(arg: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
9 | def foo_no_return_typing_extensions(
|
||||
10 | arg: typing_extensions.NoReturn,
|
||||
|
|
||||
|
||||
PYI050.pyi:10:44: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
10 | arg: typing_extensions.NoReturn,
|
||||
11 | ): ... # Error: PYI050
|
||||
12 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
13 | def foo_no_return_pos_only(arg: int, /, arg2: NoReturn): ... # Error: PYI050
|
||||
14 | def foo_never(arg: Never): ...
|
||||
|
|
||||
|
||||
PYI050.pyi:11:47: PYI050 Prefer `typing_extensions.Never` over `NoReturn` for argument annotations
|
||||
|
|
||||
11 | ): ... # Error: PYI050
|
||||
12 | def foo_no_return_kwarg(arg: int, *, arg2: NoReturn): ... # Error: PYI050
|
||||
13 | def foo_no_return_pos_only(arg: int, /, arg2: NoReturn): ... # Error: PYI050
|
||||
| ^^^^^^^^ PYI050
|
||||
14 | def foo_never(arg: Never): ...
|
||||
|
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rustpython_parser::ast::{Expr, ExprName, Ranged};
|
||||
use rustpython_parser::ast::{Expr, Ranged};
|
||||
|
||||
use ruff_diagnostics::{Diagnostic, Violation};
|
||||
use ruff_macros::{derive_message_formats, violation};
|
||||
@@ -6,7 +6,7 @@ use ruff_macros::{derive_message_formats, violation};
|
||||
use crate::checkers::ast::Checker;
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for iterations over `set` literals and comprehensions.
|
||||
/// Checks for iterations over `set` literals.
|
||||
///
|
||||
/// ## Why is this bad?
|
||||
/// Iterating over a `set` is less efficient than iterating over a sequence
|
||||
@@ -38,23 +38,7 @@ impl Violation for IterationOverSet {
|
||||
|
||||
/// PLC0208
|
||||
pub(crate) fn iteration_over_set(checker: &mut Checker, expr: &Expr) {
|
||||
let is_set = match expr {
|
||||
// Ex) `for i in {1, 2, 3}`
|
||||
Expr::Set(_) => true,
|
||||
// Ex)` for i in {n for n in range(1, 4)}`
|
||||
Expr::SetComp(_) => true,
|
||||
// Ex) `for i in set(1, 2, 3)`
|
||||
Expr::Call(call) => {
|
||||
if let Expr::Name(ExprName { id, .. }) = call.func.as_ref() {
|
||||
id.as_str() == "set" && checker.semantic_model().is_builtin("set")
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if is_set {
|
||||
if expr.is_set_expr() {
|
||||
checker
|
||||
.diagnostics
|
||||
.push(Diagnostic::new(IterationOverSet, expr.range()));
|
||||
|
||||
@@ -10,62 +10,44 @@ iteration_over_set.py:3:13: PLC0208 Use a sequence type instead of a `set` when
|
||||
6 | print(f"I like {item}.")
|
||||
|
|
||||
|
||||
iteration_over_set.py:6:13: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
6 | print(f"I like {item}.")
|
||||
7 |
|
||||
8 | for item in set(("apples", "lemons", "water")): # flags set() calls
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ PLC0208
|
||||
9 | print(f"I like {item}.")
|
||||
|
|
||||
|
||||
iteration_over_set.py:9:15: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:6:28: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
9 | print(f"I like {item}.")
|
||||
10 |
|
||||
11 | for number in {i for i in range(10)}: # flags set comprehensions
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^ PLC0208
|
||||
12 | print(number)
|
||||
|
|
||||
|
||||
iteration_over_set.py:12:28: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
12 | print(number)
|
||||
13 |
|
||||
14 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
6 | print(f"I like {item}.")
|
||||
7 |
|
||||
8 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
15 |
|
||||
16 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
9 |
|
||||
10 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
|
|
||||
|
||||
iteration_over_set.py:14:27: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:8:27: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
14 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
15 |
|
||||
16 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
8 | numbers_list = [i for i in {1, 2, 3}] # flags sets in list comprehensions
|
||||
9 |
|
||||
10 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
17 |
|
||||
18 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
11 |
|
||||
12 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
|
|
||||
|
||||
iteration_over_set.py:16:36: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:10:36: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
16 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
17 |
|
||||
18 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
10 | numbers_set = {i for i in {1, 2, 3}} # flags sets in set comprehensions
|
||||
11 |
|
||||
12 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
19 |
|
||||
20 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
13 |
|
||||
14 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
|
|
||||
|
||||
iteration_over_set.py:18:27: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
iteration_over_set.py:12:27: PLC0208 Use a sequence type instead of a `set` when iterating over values
|
||||
|
|
||||
18 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
19 |
|
||||
20 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
12 | numbers_dict = {str(i): i for i in {1, 2, 3}} # flags sets in dict comprehensions
|
||||
13 |
|
||||
14 | numbers_gen = (i for i in {1, 2, 3}) # flags sets in generator expressions
|
||||
| ^^^^^^^^^ PLC0208
|
||||
21 |
|
||||
22 | # Non-errors
|
||||
15 |
|
||||
16 | # Non-errors
|
||||
|
|
||||
|
||||
|
||||
|
||||
1
ruff.schema.json
generated
1
ruff.schema.json
generated
@@ -2254,6 +2254,7 @@
|
||||
"PYI045",
|
||||
"PYI048",
|
||||
"PYI05",
|
||||
"PYI050",
|
||||
"PYI052",
|
||||
"PYI053",
|
||||
"PYI054",
|
||||
|
||||
Reference in New Issue
Block a user