-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
from typing import Union as U | ||
|
||
|
||
def func1(arg: None | int): | ||
... | ||
|
||
|
||
def func2() -> None | int: | ||
... | ||
|
||
|
||
def func3(arg: None | None | int): | ||
... | ||
|
||
|
||
def func4(arg: U[None, int]): | ||
... | ||
|
||
|
||
def func5() -> U[None, int]: | ||
... | ||
|
||
|
||
def func6(arg: U[None, None, int]): | ||
... | ||
|
||
|
||
# Ok | ||
def good_func1(arg: int | None): | ||
... | ||
|
||
|
||
def good_func2() -> int | None: | ||
... | ||
|
||
|
||
def good_func3(arg: None): | ||
... | ||
|
||
|
||
def good_func4(arg: U[None]): | ||
... | ||
|
||
|
||
def good_func5(arg: U[int]): | ||
... | ||
|
25 changes: 25 additions & 0 deletions
25
crates/ruff_linter/resources/test/fixtures/ruff/RUF036.pyi
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from typing import Union as U | ||
|
||
|
||
def func1(arg: None | int): ... | ||
|
||
def func2() -> None | int: ... | ||
|
||
def func3(arg: None | None | int): ... | ||
|
||
def func4(arg: U[None, int]): ... | ||
|
||
def func5() -> U[None, int]: ... | ||
|
||
def func6(arg: U[None, None, int]): ... | ||
|
||
# Ok | ||
def good_func1(arg: int | None): ... | ||
|
||
def good_func2() -> int | None: ... | ||
|
||
def good_func3(arg: None): ... | ||
|
||
def good_func4(arg: U[None]): ... | ||
|
||
def good_func5(arg: U[int]): ... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
crates/ruff_linter/src/rules/ruff/rules/none_not_at_end_of_union.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
use ruff_diagnostics::{Diagnostic, Violation}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast::Expr; | ||
use ruff_python_semantic::analyze::typing::traverse_union; | ||
use ruff_text_size::Ranged; | ||
use smallvec::SmallVec; | ||
|
||
use crate::checkers::ast::Checker; | ||
|
||
/// ## What it does | ||
/// Checks for type annotations where `None` is not at the end of an union. | ||
/// | ||
/// ## Why is this bad? | ||
/// Type annotation unions are associative, meaning that the order of the elements | ||
/// does not matter. The `None` literal represents the absence of a value. For | ||
/// readability, it's preferred to write the more informative type expressions first. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// def func(arg: None | int): ... | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// def func(arg: int | None): ... | ||
/// ``` | ||
/// | ||
/// ## References | ||
/// - [Python documentation: Union type](https://docs.python.org/3/library/stdtypes.html#types-union) | ||
/// - [Python documentation: `typing.Optional`](https://docs.python.org/3/library/typing.html#typing.Optional) | ||
/// - [Python documentation: `None`](https://docs.python.org/3/library/constants.html#None) | ||
#[violation] | ||
pub struct NoneNotAtEndOfUnion; | ||
|
||
impl Violation for NoneNotAtEndOfUnion { | ||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
"`None` not at the end of the type annotation.".to_string() | ||
} | ||
} | ||
|
||
/// RUF036 | ||
pub(crate) fn none_not_at_end_of_union<'a>(checker: &mut Checker, union: &'a Expr) { | ||
let semantic = checker.semantic(); | ||
let mut none_exprs: SmallVec<[&Expr; 1]> = SmallVec::new(); | ||
|
||
let mut last_expr: Option<&Expr> = None; | ||
let mut find_none = |expr: &'a Expr, _parent: &Expr| { | ||
if matches!(expr, Expr::NoneLiteral(_)) { | ||
none_exprs.push(expr); | ||
} | ||
last_expr = Some(expr); | ||
}; | ||
|
||
// Walk through all type expressions in the union and keep track of `None` literals. | ||
traverse_union(&mut find_none, semantic, union); | ||
|
||
let Some(last_expr) = last_expr else { | ||
return; | ||
}; | ||
|
||
// The must be at least one `None` expression. | ||
let Some(last_none) = none_exprs.last() else { | ||
return; | ||
}; | ||
|
||
// If any of the `None` literals is last we do not emit. | ||
if *last_none == last_expr { | ||
return; | ||
} | ||
|
||
for none_expr in none_exprs { | ||
checker | ||
.diagnostics | ||
.push(Diagnostic::new(NoneNotAtEndOfUnion, none_expr.range())); | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
...ff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.py.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/ruff/mod.rs | ||
--- | ||
RUF036.py:4:16: RUF036 `None` not at the end of the type annotation. | ||
| | ||
4 | def func1(arg: None | int): | ||
| ^^^^ RUF036 | ||
5 | ... | ||
| | ||
|
||
RUF036.py:8:16: RUF036 `None` not at the end of the type annotation. | ||
| | ||
8 | def func2() -> None | int: | ||
| ^^^^ RUF036 | ||
9 | ... | ||
| | ||
|
||
RUF036.py:12:16: RUF036 `None` not at the end of the type annotation. | ||
| | ||
12 | def func3(arg: None | None | int): | ||
| ^^^^ RUF036 | ||
13 | ... | ||
| | ||
|
||
RUF036.py:12:23: RUF036 `None` not at the end of the type annotation. | ||
| | ||
12 | def func3(arg: None | None | int): | ||
| ^^^^ RUF036 | ||
13 | ... | ||
| | ||
|
||
RUF036.py:16:18: RUF036 `None` not at the end of the type annotation. | ||
| | ||
16 | def func4(arg: U[None, int]): | ||
| ^^^^ RUF036 | ||
17 | ... | ||
| | ||
|
||
RUF036.py:20:18: RUF036 `None` not at the end of the type annotation. | ||
| | ||
20 | def func5() -> U[None, int]: | ||
| ^^^^ RUF036 | ||
21 | ... | ||
| | ||
|
||
RUF036.py:24:18: RUF036 `None` not at the end of the type annotation. | ||
| | ||
24 | def func6(arg: U[None, None, int]): | ||
| ^^^^ RUF036 | ||
25 | ... | ||
| | ||
|
||
RUF036.py:24:24: RUF036 `None` not at the end of the type annotation. | ||
| | ||
24 | def func6(arg: U[None, None, int]): | ||
| ^^^^ RUF036 | ||
25 | ... | ||
| |
80 changes: 80 additions & 0 deletions
80
...f_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF036_RUF036.pyi.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/ruff/mod.rs | ||
--- | ||
RUF036.pyi:4:16: RUF036 `None` not at the end of the type annotation. | ||
| | ||
4 | def func1(arg: None | int): ... | ||
| ^^^^ RUF036 | ||
5 | | ||
6 | def func2() -> None | int: ... | ||
| | ||
|
||
RUF036.pyi:6:16: RUF036 `None` not at the end of the type annotation. | ||
| | ||
4 | def func1(arg: None | int): ... | ||
5 | | ||
6 | def func2() -> None | int: ... | ||
| ^^^^ RUF036 | ||
7 | | ||
8 | def func3(arg: None | None | int): ... | ||
| | ||
|
||
RUF036.pyi:8:16: RUF036 `None` not at the end of the type annotation. | ||
| | ||
6 | def func2() -> None | int: ... | ||
7 | | ||
8 | def func3(arg: None | None | int): ... | ||
| ^^^^ RUF036 | ||
9 | | ||
10 | def func4(arg: U[None, int]): ... | ||
| | ||
|
||
RUF036.pyi:8:23: RUF036 `None` not at the end of the type annotation. | ||
| | ||
6 | def func2() -> None | int: ... | ||
7 | | ||
8 | def func3(arg: None | None | int): ... | ||
| ^^^^ RUF036 | ||
9 | | ||
10 | def func4(arg: U[None, int]): ... | ||
| | ||
|
||
RUF036.pyi:10:18: RUF036 `None` not at the end of the type annotation. | ||
| | ||
8 | def func3(arg: None | None | int): ... | ||
9 | | ||
10 | def func4(arg: U[None, int]): ... | ||
| ^^^^ RUF036 | ||
11 | | ||
12 | def func5() -> U[None, int]: ... | ||
| | ||
|
||
RUF036.pyi:12:18: RUF036 `None` not at the end of the type annotation. | ||
| | ||
10 | def func4(arg: U[None, int]): ... | ||
11 | | ||
12 | def func5() -> U[None, int]: ... | ||
| ^^^^ RUF036 | ||
13 | | ||
14 | def func6(arg: U[None, None, int]): ... | ||
| | ||
|
||
RUF036.pyi:14:18: RUF036 `None` not at the end of the type annotation. | ||
| | ||
12 | def func5() -> U[None, int]: ... | ||
13 | | ||
14 | def func6(arg: U[None, None, int]): ... | ||
| ^^^^ RUF036 | ||
15 | | ||
16 | # Ok | ||
| | ||
|
||
RUF036.pyi:14:24: RUF036 `None` not at the end of the type annotation. | ||
| | ||
12 | def func5() -> U[None, int]: ... | ||
13 | | ||
14 | def func6(arg: U[None, None, int]): ... | ||
| ^^^^ RUF036 | ||
15 | | ||
16 | # Ok | ||
| |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.