-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
1 parent
1a392d3
commit 56b4c47
Showing
12 changed files
with
451 additions
and
0 deletions.
There are no files selected for viewing
21 changes: 21 additions & 0 deletions
21
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.py
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,21 @@ | ||
from typing import Literal | ||
import typing as t | ||
import typing_extensions | ||
|
||
x: Literal[True, False, True, False] # PYI062 twice here | ||
|
||
y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 | ||
|
||
z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal | ||
|
||
Literal[1, Literal[1]] # once | ||
Literal[1, 2, Literal[1, 2]] # twice | ||
Literal[1, Literal[1], Literal[1]] # twice | ||
Literal[1, Literal[2], Literal[2]] # once | ||
t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ||
typing_extensions.Literal[1, 1, 1] # twice | ||
|
||
# Ensure issue is only raised once, even on nested literals | ||
MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 | ||
|
||
n: Literal["No", "duplicates", "here", 1, "1"] |
21 changes: 21 additions & 0 deletions
21
crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI062.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,21 @@ | ||
from typing import Literal | ||
import typing as t | ||
import typing_extensions | ||
|
||
x: Literal[True, False, True, False] # PY062 twice here | ||
|
||
y: Literal[1, print("hello"), 3, Literal[4, 1]] # PY062 on the last 1 | ||
|
||
z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PY062 on the set literal | ||
|
||
Literal[1, Literal[1]] # once | ||
Literal[1, 2, Literal[1, 2]] # twice | ||
Literal[1, Literal[1], Literal[1]] # twice | ||
Literal[1, Literal[2], Literal[2]] # once | ||
t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ||
typing_extensions.Literal[1, 1, 1] # twice | ||
|
||
# Ensure issue is only raised once, even on nested literals | ||
MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 | ||
|
||
n: Literal["No", "duplicates", "here", 1, "1"] |
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
67 changes: 67 additions & 0 deletions
67
crates/ruff_linter/src/rules/flake8_pyi/rules/duplicate_literal_member.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,67 @@ | ||
use std::collections::HashSet; | ||
|
||
use rustc_hash::FxHashSet; | ||
|
||
use ruff_diagnostics::{Diagnostic, FixAvailability, Violation}; | ||
use ruff_macros::{derive_message_formats, violation}; | ||
use ruff_python_ast::comparable::ComparableExpr; | ||
use ruff_python_ast::Expr; | ||
use ruff_python_semantic::analyze::typing::traverse_literal; | ||
use ruff_text_size::Ranged; | ||
|
||
use crate::checkers::ast::Checker; | ||
|
||
/// ## What it does | ||
/// Checks for duplicate members in a `typing.Literal[]` slice. | ||
/// | ||
/// ## Why is this bad? | ||
/// Duplicate literal members are redundant and should be removed. | ||
/// | ||
/// ## Example | ||
/// ```python | ||
/// foo: Literal["a", "b", "a"] | ||
/// ``` | ||
/// | ||
/// Use instead: | ||
/// ```python | ||
/// foo: Literal["a", "b"] | ||
/// ``` | ||
/// | ||
/// ## References | ||
/// - [Python documentation: `typing.Literal`](https://docs.python.org/3/library/typing.html#typing.Literal) | ||
#[violation] | ||
pub struct DuplicateLiteralMember { | ||
duplicate_name: String, | ||
} | ||
|
||
impl Violation for DuplicateLiteralMember { | ||
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; | ||
|
||
#[derive_message_formats] | ||
fn message(&self) -> String { | ||
format!("Duplicate literal member `{}`", self.duplicate_name) | ||
} | ||
} | ||
|
||
/// PYI062 | ||
pub(crate) fn duplicate_literal_member<'a>(checker: &mut Checker, expr: &'a Expr) { | ||
let mut seen_nodes: HashSet<ComparableExpr<'_>, _> = FxHashSet::default(); | ||
let mut diagnostics: Vec<Diagnostic> = Vec::new(); | ||
|
||
// Adds a member to `literal_exprs` if it is a `Literal` annotation | ||
let mut check_for_duplicate_members = |expr: &'a Expr, _: &'a Expr| { | ||
// If we've already seen this literal member, raise a violation. | ||
if !seen_nodes.insert(expr.into()) { | ||
diagnostics.push(Diagnostic::new( | ||
DuplicateLiteralMember { | ||
duplicate_name: checker.generator().expr(expr), | ||
}, | ||
expr.range(), | ||
)); | ||
} | ||
}; | ||
|
||
// Traverse the literal, collect all diagnostic members | ||
traverse_literal(&mut check_for_duplicate_members, checker.semantic(), expr); | ||
checker.diagnostics.append(&mut diagnostics); | ||
} |
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
138 changes: 138 additions & 0 deletions
138
...c/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI062_PYI062.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,138 @@ | ||
--- | ||
source: crates/ruff_linter/src/rules/flake8_pyi/mod.rs | ||
--- | ||
PYI062.py:5:25: PYI062 Duplicate literal member `True` | ||
| | ||
3 | import typing_extensions | ||
4 | | ||
5 | x: Literal[True, False, True, False] # PYI062 twice here | ||
| ^^^^ PYI062 | ||
6 | | ||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 | ||
| | ||
|
||
PYI062.py:5:31: PYI062 Duplicate literal member `False` | ||
| | ||
3 | import typing_extensions | ||
4 | | ||
5 | x: Literal[True, False, True, False] # PYI062 twice here | ||
| ^^^^^ PYI062 | ||
6 | | ||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 | ||
| | ||
|
||
PYI062.py:7:45: PYI062 Duplicate literal member `1` | ||
| | ||
5 | x: Literal[True, False, True, False] # PYI062 twice here | ||
6 | | ||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 | ||
| ^ PYI062 | ||
8 | | ||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal | ||
| | ||
|
||
PYI062.py:9:33: PYI062 Duplicate literal member `{1, 3, 5}` | ||
| | ||
7 | y: Literal[1, print("hello"), 3, Literal[4, 1]] # PYI062 on the last 1 | ||
8 | | ||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal | ||
| ^^^^^^^ PYI062 | ||
10 | | ||
11 | Literal[1, Literal[1]] # once | ||
| | ||
|
||
PYI062.py:11:20: PYI062 Duplicate literal member `1` | ||
| | ||
9 | z: Literal[{1, 3, 5}, "foobar", {1,3,5}] # PYI062 on the set literal | ||
10 | | ||
11 | Literal[1, Literal[1]] # once | ||
| ^ PYI062 | ||
12 | Literal[1, 2, Literal[1, 2]] # twice | ||
13 | Literal[1, Literal[1], Literal[1]] # twice | ||
| | ||
|
||
PYI062.py:12:23: PYI062 Duplicate literal member `1` | ||
| | ||
11 | Literal[1, Literal[1]] # once | ||
12 | Literal[1, 2, Literal[1, 2]] # twice | ||
| ^ PYI062 | ||
13 | Literal[1, Literal[1], Literal[1]] # twice | ||
14 | Literal[1, Literal[2], Literal[2]] # once | ||
| | ||
|
||
PYI062.py:12:26: PYI062 Duplicate literal member `2` | ||
| | ||
11 | Literal[1, Literal[1]] # once | ||
12 | Literal[1, 2, Literal[1, 2]] # twice | ||
| ^ PYI062 | ||
13 | Literal[1, Literal[1], Literal[1]] # twice | ||
14 | Literal[1, Literal[2], Literal[2]] # once | ||
| | ||
|
||
PYI062.py:13:20: PYI062 Duplicate literal member `1` | ||
| | ||
11 | Literal[1, Literal[1]] # once | ||
12 | Literal[1, 2, Literal[1, 2]] # twice | ||
13 | Literal[1, Literal[1], Literal[1]] # twice | ||
| ^ PYI062 | ||
14 | Literal[1, Literal[2], Literal[2]] # once | ||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ||
| | ||
|
||
PYI062.py:13:32: PYI062 Duplicate literal member `1` | ||
| | ||
11 | Literal[1, Literal[1]] # once | ||
12 | Literal[1, 2, Literal[1, 2]] # twice | ||
13 | Literal[1, Literal[1], Literal[1]] # twice | ||
| ^ PYI062 | ||
14 | Literal[1, Literal[2], Literal[2]] # once | ||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ||
| | ||
|
||
PYI062.py:14:32: PYI062 Duplicate literal member `2` | ||
| | ||
12 | Literal[1, 2, Literal[1, 2]] # twice | ||
13 | Literal[1, Literal[1], Literal[1]] # twice | ||
14 | Literal[1, Literal[2], Literal[2]] # once | ||
| ^ PYI062 | ||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ||
16 | typing_extensions.Literal[1, 1, 1] # twice | ||
| | ||
|
||
PYI062.py:15:37: PYI062 Duplicate literal member `1` | ||
| | ||
13 | Literal[1, Literal[1], Literal[1]] # twice | ||
14 | Literal[1, Literal[2], Literal[2]] # once | ||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ||
| ^ PYI062 | ||
16 | typing_extensions.Literal[1, 1, 1] # twice | ||
| | ||
|
||
PYI062.py:16:30: PYI062 Duplicate literal member `1` | ||
| | ||
14 | Literal[1, Literal[2], Literal[2]] # once | ||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ||
16 | typing_extensions.Literal[1, 1, 1] # twice | ||
| ^ PYI062 | ||
17 | | ||
18 | # Ensure issue is only raised once, even on nested literals | ||
| | ||
|
||
PYI062.py:16:33: PYI062 Duplicate literal member `1` | ||
| | ||
14 | Literal[1, Literal[2], Literal[2]] # once | ||
15 | t.Literal[1, t.Literal[2, t.Literal[1]]] # once | ||
16 | typing_extensions.Literal[1, 1, 1] # twice | ||
| ^ PYI062 | ||
17 | | ||
18 | # Ensure issue is only raised once, even on nested literals | ||
| | ||
|
||
PYI062.py:19:46: PYI062 Duplicate literal member `True` | ||
| | ||
18 | # Ensure issue is only raised once, even on nested literals | ||
19 | MyType = Literal["foo", Literal[True, False, True], "bar"] # PYI062 | ||
| ^^^^ PYI062 | ||
20 | | ||
21 | n: Literal["No", "duplicates", "here", 1, "1"] | ||
| |
Oops, something went wrong.