diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF041.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF041.py new file mode 100644 index 0000000000..44adc4e364 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF041.py @@ -0,0 +1,31 @@ +from typing import Literal +import typing as t +import typing_extensions + + +y: Literal[1, print("hello"), 3, Literal[4, 1]] +Literal[1, Literal[1]] +Literal[1, 2, Literal[1, 2]] +Literal[1, Literal[1], Literal[1]] +Literal[1, Literal[2], Literal[2]] +t.Literal[1, t.Literal[2, t.Literal[1]]] +Literal[ + 1, # comment 1 + Literal[ # another comment + 1 # yet annother comment + ] +] # once + +# Ensure issue is only raised once, even on nested literals +MyType = Literal["foo", Literal[True, False, True], "bar"] + +# nested literals, all equivalent to `Literal[1]` +Literal[Literal[1]] +Literal[Literal[Literal[1], Literal[1]]] +Literal[Literal[1], Literal[Literal[Literal[1]]]] + +# OK +x: Literal[True, False, True, False] +z: Literal[{1, 3, 5}, "foobar", {1,3,5}] +typing_extensions.Literal[1, 1, 1] +n: Literal["No", "duplicates", "here", 1, "1"] diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF041.pyi b/crates/ruff_linter/resources/test/fixtures/ruff/RUF041.pyi new file mode 100644 index 0000000000..44adc4e364 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF041.pyi @@ -0,0 +1,31 @@ +from typing import Literal +import typing as t +import typing_extensions + + +y: Literal[1, print("hello"), 3, Literal[4, 1]] +Literal[1, Literal[1]] +Literal[1, 2, Literal[1, 2]] +Literal[1, Literal[1], Literal[1]] +Literal[1, Literal[2], Literal[2]] +t.Literal[1, t.Literal[2, t.Literal[1]]] +Literal[ + 1, # comment 1 + Literal[ # another comment + 1 # yet annother comment + ] +] # once + +# Ensure issue is only raised once, even on nested literals +MyType = Literal["foo", Literal[True, False, True], "bar"] + +# nested literals, all equivalent to `Literal[1]` +Literal[Literal[1]] +Literal[Literal[Literal[1], Literal[1]]] +Literal[Literal[1], Literal[Literal[Literal[1]]]] + +# OK +x: Literal[True, False, True, False] +z: Literal[{1, 3, 5}, "foobar", {1,3,5}] +typing_extensions.Literal[1, 1, 1] +n: Literal["No", "duplicates", "here", 1, "1"] diff --git a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs index 6ca710fd8f..4a0c1f8724 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/expression.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/expression.rs @@ -108,6 +108,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { Rule::DuplicateLiteralMember, Rule::RedundantBoolLiteral, Rule::RedundantNoneLiteral, + Rule::UnnecessaryNestedLiteral, ]) { if !checker.semantic.in_nested_literal() { if checker.enabled(Rule::DuplicateLiteralMember) { @@ -119,6 +120,9 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) { if checker.enabled(Rule::RedundantNoneLiteral) { flake8_pyi::rules::redundant_none_literal(checker, expr); } + if checker.enabled(Rule::UnnecessaryNestedLiteral) { + ruff::rules::unnecessary_nested_literal(checker, expr); + } } } diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index d4a9ec9641..a52c57b618 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -980,9 +980,10 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (Ruff, "035") => (RuleGroup::Preview, rules::ruff::rules::UnsafeMarkupUse), (Ruff, "036") => (RuleGroup::Preview, rules::ruff::rules::NoneNotAtEndOfUnion), (Ruff, "038") => (RuleGroup::Preview, rules::ruff::rules::RedundantBoolLiteral), - (Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing), (Ruff, "039") => (RuleGroup::Preview, rules::ruff::rules::UnrawRePattern), (Ruff, "040") => (RuleGroup::Preview, rules::ruff::rules::InvalidAssertMessageLiteralArgument), + (Ruff, "041") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryNestedLiteral), + (Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing), (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA), (Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA), diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index aad87d1d33..daf244f807 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -67,6 +67,8 @@ mod tests { #[test_case(Rule::RedundantBoolLiteral, Path::new("RUF038.py"))] #[test_case(Rule::RedundantBoolLiteral, Path::new("RUF038.pyi"))] #[test_case(Rule::InvalidAssertMessageLiteralArgument, Path::new("RUF040.py"))] + #[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))] + #[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))] fn rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy()); let diagnostics = test_path( diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 95f79145d5..33c98e4e6a 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -32,6 +32,7 @@ pub(crate) use static_key_dict_comprehension::*; pub(crate) use test_rules::*; pub(crate) use unnecessary_iterable_allocation_for_first_element::*; pub(crate) use unnecessary_key_check::*; +pub(crate) use unnecessary_nested_literal::*; pub(crate) use unraw_re_pattern::*; pub(crate) use unsafe_markup_use::*; pub(crate) use unused_async::*; @@ -77,6 +78,7 @@ mod suppression_comment_visitor; pub(crate) mod test_rules; mod unnecessary_iterable_allocation_for_first_element; mod unnecessary_key_check; +mod unnecessary_nested_literal; mod unraw_re_pattern; mod unsafe_markup_use; mod unused_async; diff --git a/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs new file mode 100644 index 0000000000..122579474f --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/unnecessary_nested_literal.rs @@ -0,0 +1,139 @@ +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast::{AnyNodeRef, Expr, ExprContext, ExprSubscript, ExprTuple}; +use ruff_python_semantic::analyze::typing::traverse_literal; +use ruff_text_size::{Ranged, TextRange}; + +use crate::checkers::ast::Checker; + +/// ## What it does +/// Checks for unnecessary nested `Literal`. +/// +/// ## Why is this bad? +/// Prefer using a single `Literal`, which is equivalent and more concise. +/// +/// Parameterization of literals by other literals is supported as an ergonomic +/// feature as proposed in [PEP 586], to enable patterns such as: +/// ```python +/// ReadOnlyMode = Literal["r", "r+"] +/// WriteAndTruncateMode = Literal["w", "w+", "wt", "w+t"] +/// WriteNoTruncateMode = Literal["r+", "r+t"] +/// AppendMode = Literal["a", "a+", "at", "a+t"] +/// +/// AllModes = Literal[ReadOnlyMode, WriteAndTruncateMode, +/// WriteNoTruncateMode, AppendMode] +/// ``` +/// +/// As a consequence, type checkers also support nesting of literals +/// which is less readable than a flat `Literal`: +/// ```python +/// AllModes = Literal[Literal["r", "r+"], Literal["w", "w+", "wt", "w+t"], +/// Literal["r+", "r+t"], Literal["a", "a+", "at", "a+t"]] +/// ``` +/// +/// ## Example +/// ```python +/// AllModes = Literal[ +/// Literal["r", "r+"], +/// Literal["w", "w+", "wt", "w+t"], +/// Literal["r+", "r+t"], +/// Literal["a", "a+", "at", "a+t"], +/// ] +/// ``` +/// +/// Use instead: +/// ```python +/// AllModes = Literal[ +/// "r", "r+", "w", "w+", "wt", "w+t", "r+", "r+t", "a", "a+", "at", "a+t" +/// ] +/// ``` +/// +/// or assign the literal to a variable as in the first example. +/// +/// ## Fix safety +/// The fix for this rule is marked as unsafe when the `Literal` slice is split +/// across multiple lines and some of the lines have trailing comments. +/// +/// ## References +/// - [Typing documentation: Legal parameters for `Literal` at type check time](https://typing.readthedocs.io/en/latest/spec/literal.html#legal-parameters-for-literal-at-type-check-time) +/// +/// [PEP 586](https://peps.python.org/pep-0586/) +#[derive(ViolationMetadata)] +pub(crate) struct UnnecessaryNestedLiteral; + +impl Violation for UnnecessaryNestedLiteral { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + + #[derive_message_formats] + fn message(&self) -> String { + "Unnecessary nested `Literal`".to_string() + } + + fn fix_title(&self) -> Option { + Some("Replace with flattened `Literal`".to_string()) + } +} + +/// RUF039 +pub(crate) fn unnecessary_nested_literal<'a>(checker: &mut Checker, literal_expr: &'a Expr) { + let mut is_nested = false; + + // Traverse the type expressions in the `Literal`. + traverse_literal( + &mut |_: &'a Expr, parent: &'a Expr| { + // If the parent is not equal to the `literal_expr` then we know we are traversing recursively. + if !AnyNodeRef::ptr_eq(parent.into(), literal_expr.into()) { + is_nested = true; + }; + }, + checker.semantic(), + literal_expr, + ); + + if !is_nested { + return; + } + + // Collect the literal nodes for the fix + let mut nodes: Vec<&Expr> = Vec::new(); + + traverse_literal( + &mut |expr, _| { + nodes.push(expr); + }, + checker.semantic(), + literal_expr, + ); + + let mut diagnostic = Diagnostic::new(UnnecessaryNestedLiteral, literal_expr.range()); + + // Create a [`Fix`] that flattens all nodes. + if let Expr::Subscript(subscript) = literal_expr { + let subscript = Expr::Subscript(ExprSubscript { + slice: Box::new(if let [elt] = nodes.as_slice() { + (*elt).clone() + } else { + Expr::Tuple(ExprTuple { + elts: nodes.into_iter().cloned().collect(), + range: TextRange::default(), + ctx: ExprContext::Load, + parenthesized: false, + }) + }), + value: subscript.value.clone(), + range: TextRange::default(), + ctx: ExprContext::Load, + }); + let fix = Fix::applicable_edit( + Edit::range_replacement(checker.generator().expr(&subscript), literal_expr.range()), + if checker.comment_ranges().intersects(literal_expr.range()) { + Applicability::Unsafe + } else { + Applicability::Safe + }, + ); + diagnostic.set_fix(fix); + }; + + checker.diagnostics.push(diagnostic); +} diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF041_RUF041.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF041_RUF041.py.snap new file mode 100644 index 0000000000..9de9644bcf --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF041_RUF041.py.snap @@ -0,0 +1,279 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +snapshot_kind: text +--- +RUF041.py:6:4: RUF041 [*] Unnecessary nested `Literal` + | +6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +7 | Literal[1, Literal[1]] +8 | Literal[1, 2, Literal[1, 2]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +3 3 | import typing_extensions +4 4 | +5 5 | +6 |-y: Literal[1, print("hello"), 3, Literal[4, 1]] + 6 |+y: Literal[1, print("hello"), 3, 4, 1] +7 7 | Literal[1, Literal[1]] +8 8 | Literal[1, 2, Literal[1, 2]] +9 9 | Literal[1, Literal[1], Literal[1]] + +RUF041.py:7:1: RUF041 [*] Unnecessary nested `Literal` + | +6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] +7 | Literal[1, Literal[1]] + | ^^^^^^^^^^^^^^^^^^^^^^ RUF041 +8 | Literal[1, 2, Literal[1, 2]] +9 | Literal[1, Literal[1], Literal[1]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +4 4 | +5 5 | +6 6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] +7 |-Literal[1, Literal[1]] + 7 |+Literal[1, 1] +8 8 | Literal[1, 2, Literal[1, 2]] +9 9 | Literal[1, Literal[1], Literal[1]] +10 10 | Literal[1, Literal[2], Literal[2]] + +RUF041.py:8:1: RUF041 [*] Unnecessary nested `Literal` + | + 6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] + 7 | Literal[1, Literal[1]] + 8 | Literal[1, 2, Literal[1, 2]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 + 9 | Literal[1, Literal[1], Literal[1]] +10 | Literal[1, Literal[2], Literal[2]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +5 5 | +6 6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] +7 7 | Literal[1, Literal[1]] +8 |-Literal[1, 2, Literal[1, 2]] + 8 |+Literal[1, 2, 1, 2] +9 9 | Literal[1, Literal[1], Literal[1]] +10 10 | Literal[1, Literal[2], Literal[2]] +11 11 | t.Literal[1, t.Literal[2, t.Literal[1]]] + +RUF041.py:9:1: RUF041 [*] Unnecessary nested `Literal` + | + 7 | Literal[1, Literal[1]] + 8 | Literal[1, 2, Literal[1, 2]] + 9 | Literal[1, Literal[1], Literal[1]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +10 | Literal[1, Literal[2], Literal[2]] +11 | t.Literal[1, t.Literal[2, t.Literal[1]]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +6 6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] +7 7 | Literal[1, Literal[1]] +8 8 | Literal[1, 2, Literal[1, 2]] +9 |-Literal[1, Literal[1], Literal[1]] + 9 |+Literal[1, 1, 1] +10 10 | Literal[1, Literal[2], Literal[2]] +11 11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 12 | Literal[ + +RUF041.py:10:1: RUF041 [*] Unnecessary nested `Literal` + | + 8 | Literal[1, 2, Literal[1, 2]] + 9 | Literal[1, Literal[1], Literal[1]] +10 | Literal[1, Literal[2], Literal[2]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 | Literal[ + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +7 7 | Literal[1, Literal[1]] +8 8 | Literal[1, 2, Literal[1, 2]] +9 9 | Literal[1, Literal[1], Literal[1]] +10 |-Literal[1, Literal[2], Literal[2]] + 10 |+Literal[1, 2, 2] +11 11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 12 | Literal[ +13 13 | 1, # comment 1 + +RUF041.py:11:1: RUF041 [*] Unnecessary nested `Literal` + | + 9 | Literal[1, Literal[1], Literal[1]] +10 | Literal[1, Literal[2], Literal[2]] +11 | t.Literal[1, t.Literal[2, t.Literal[1]]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +12 | Literal[ +13 | 1, # comment 1 + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +8 8 | Literal[1, 2, Literal[1, 2]] +9 9 | Literal[1, Literal[1], Literal[1]] +10 10 | Literal[1, Literal[2], Literal[2]] +11 |-t.Literal[1, t.Literal[2, t.Literal[1]]] + 11 |+t.Literal[1, 2, 1] +12 12 | Literal[ +13 13 | 1, # comment 1 +14 14 | Literal[ # another comment + +RUF041.py:12:1: RUF041 [*] Unnecessary nested `Literal` + | +10 | Literal[1, Literal[2], Literal[2]] +11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 | / Literal[ +13 | | 1, # comment 1 +14 | | Literal[ # another comment +15 | | 1 # yet annother comment +16 | | ] +17 | | ] # once + | |_^ RUF041 +18 | +19 | # Ensure issue is only raised once, even on nested literals + | + = help: Replace with flattened `Literal` + +ℹ Unsafe fix +9 9 | Literal[1, Literal[1], Literal[1]] +10 10 | Literal[1, Literal[2], Literal[2]] +11 11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 |-Literal[ +13 |- 1, # comment 1 +14 |- Literal[ # another comment +15 |- 1 # yet annother comment +16 |- ] +17 |-] # once + 12 |+Literal[1, 1] # once +18 13 | +19 14 | # Ensure issue is only raised once, even on nested literals +20 15 | MyType = Literal["foo", Literal[True, False, True], "bar"] + +RUF041.py:20:10: RUF041 [*] Unnecessary nested `Literal` + | +19 | # Ensure issue is only raised once, even on nested literals +20 | MyType = Literal["foo", Literal[True, False, True], "bar"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +21 | +22 | # nested literals, all equivalent to `Literal[1]` + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +17 17 | ] # once +18 18 | +19 19 | # Ensure issue is only raised once, even on nested literals +20 |-MyType = Literal["foo", Literal[True, False, True], "bar"] + 20 |+MyType = Literal["foo", True, False, True, "bar"] +21 21 | +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] + +RUF041.py:23:1: RUF041 [*] Unnecessary nested `Literal` + | +22 | # nested literals, all equivalent to `Literal[1]` +23 | Literal[Literal[1]] + | ^^^^^^^^^^^^^^^^^^^ RUF041 +24 | Literal[Literal[Literal[1], Literal[1]]] +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +20 20 | MyType = Literal["foo", Literal[True, False, True], "bar"] +21 21 | +22 22 | # nested literals, all equivalent to `Literal[1]` +23 |-Literal[Literal[1]] + 23 |+Literal[1] +24 24 | Literal[Literal[Literal[1], Literal[1]]] +25 25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] +26 26 | + +RUF041.py:24:1: RUF041 [*] Unnecessary nested `Literal` + | +22 | # nested literals, all equivalent to `Literal[1]` +23 | Literal[Literal[1]] +24 | Literal[Literal[Literal[1], Literal[1]]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +21 21 | +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] +24 |-Literal[Literal[Literal[1], Literal[1]]] + 24 |+Literal[1, 1] +25 25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] +26 26 | +27 27 | # OK + +RUF041.py:24:9: RUF041 [*] Unnecessary nested `Literal` + | +22 | # nested literals, all equivalent to `Literal[1]` +23 | Literal[Literal[1]] +24 | Literal[Literal[Literal[1], Literal[1]]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +21 21 | +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] +24 |-Literal[Literal[Literal[1], Literal[1]]] + 24 |+Literal[Literal[1, 1]] +25 25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] +26 26 | +27 27 | # OK + +RUF041.py:25:1: RUF041 [*] Unnecessary nested `Literal` + | +23 | Literal[Literal[1]] +24 | Literal[Literal[Literal[1], Literal[1]]] +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +26 | +27 | # OK + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] +24 24 | Literal[Literal[Literal[1], Literal[1]]] +25 |-Literal[Literal[1], Literal[Literal[Literal[1]]]] + 25 |+Literal[1, 1] +26 26 | +27 27 | # OK +28 28 | x: Literal[True, False, True, False] + +RUF041.py:25:29: RUF041 [*] Unnecessary nested `Literal` + | +23 | Literal[Literal[1]] +24 | Literal[Literal[Literal[1], Literal[1]]] +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | ^^^^^^^^^^^^^^^^^^^ RUF041 +26 | +27 | # OK + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] +24 24 | Literal[Literal[Literal[1], Literal[1]]] +25 |-Literal[Literal[1], Literal[Literal[Literal[1]]]] + 25 |+Literal[Literal[1], Literal[Literal[1]]] +26 26 | +27 27 | # OK +28 28 | x: Literal[True, False, True, False] diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF041_RUF041.pyi.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF041_RUF041.pyi.snap new file mode 100644 index 0000000000..c2f26280bf --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__RUF041_RUF041.pyi.snap @@ -0,0 +1,279 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +snapshot_kind: text +--- +RUF041.pyi:6:4: RUF041 [*] Unnecessary nested `Literal` + | +6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +7 | Literal[1, Literal[1]] +8 | Literal[1, 2, Literal[1, 2]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +3 3 | import typing_extensions +4 4 | +5 5 | +6 |-y: Literal[1, print("hello"), 3, Literal[4, 1]] + 6 |+y: Literal[1, print("hello"), 3, 4, 1] +7 7 | Literal[1, Literal[1]] +8 8 | Literal[1, 2, Literal[1, 2]] +9 9 | Literal[1, Literal[1], Literal[1]] + +RUF041.pyi:7:1: RUF041 [*] Unnecessary nested `Literal` + | +6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] +7 | Literal[1, Literal[1]] + | ^^^^^^^^^^^^^^^^^^^^^^ RUF041 +8 | Literal[1, 2, Literal[1, 2]] +9 | Literal[1, Literal[1], Literal[1]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +4 4 | +5 5 | +6 6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] +7 |-Literal[1, Literal[1]] + 7 |+Literal[1, 1] +8 8 | Literal[1, 2, Literal[1, 2]] +9 9 | Literal[1, Literal[1], Literal[1]] +10 10 | Literal[1, Literal[2], Literal[2]] + +RUF041.pyi:8:1: RUF041 [*] Unnecessary nested `Literal` + | + 6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] + 7 | Literal[1, Literal[1]] + 8 | Literal[1, 2, Literal[1, 2]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 + 9 | Literal[1, Literal[1], Literal[1]] +10 | Literal[1, Literal[2], Literal[2]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +5 5 | +6 6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] +7 7 | Literal[1, Literal[1]] +8 |-Literal[1, 2, Literal[1, 2]] + 8 |+Literal[1, 2, 1, 2] +9 9 | Literal[1, Literal[1], Literal[1]] +10 10 | Literal[1, Literal[2], Literal[2]] +11 11 | t.Literal[1, t.Literal[2, t.Literal[1]]] + +RUF041.pyi:9:1: RUF041 [*] Unnecessary nested `Literal` + | + 7 | Literal[1, Literal[1]] + 8 | Literal[1, 2, Literal[1, 2]] + 9 | Literal[1, Literal[1], Literal[1]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +10 | Literal[1, Literal[2], Literal[2]] +11 | t.Literal[1, t.Literal[2, t.Literal[1]]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +6 6 | y: Literal[1, print("hello"), 3, Literal[4, 1]] +7 7 | Literal[1, Literal[1]] +8 8 | Literal[1, 2, Literal[1, 2]] +9 |-Literal[1, Literal[1], Literal[1]] + 9 |+Literal[1, 1, 1] +10 10 | Literal[1, Literal[2], Literal[2]] +11 11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 12 | Literal[ + +RUF041.pyi:10:1: RUF041 [*] Unnecessary nested `Literal` + | + 8 | Literal[1, 2, Literal[1, 2]] + 9 | Literal[1, Literal[1], Literal[1]] +10 | Literal[1, Literal[2], Literal[2]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 | Literal[ + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +7 7 | Literal[1, Literal[1]] +8 8 | Literal[1, 2, Literal[1, 2]] +9 9 | Literal[1, Literal[1], Literal[1]] +10 |-Literal[1, Literal[2], Literal[2]] + 10 |+Literal[1, 2, 2] +11 11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 12 | Literal[ +13 13 | 1, # comment 1 + +RUF041.pyi:11:1: RUF041 [*] Unnecessary nested `Literal` + | + 9 | Literal[1, Literal[1], Literal[1]] +10 | Literal[1, Literal[2], Literal[2]] +11 | t.Literal[1, t.Literal[2, t.Literal[1]]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +12 | Literal[ +13 | 1, # comment 1 + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +8 8 | Literal[1, 2, Literal[1, 2]] +9 9 | Literal[1, Literal[1], Literal[1]] +10 10 | Literal[1, Literal[2], Literal[2]] +11 |-t.Literal[1, t.Literal[2, t.Literal[1]]] + 11 |+t.Literal[1, 2, 1] +12 12 | Literal[ +13 13 | 1, # comment 1 +14 14 | Literal[ # another comment + +RUF041.pyi:12:1: RUF041 [*] Unnecessary nested `Literal` + | +10 | Literal[1, Literal[2], Literal[2]] +11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 | / Literal[ +13 | | 1, # comment 1 +14 | | Literal[ # another comment +15 | | 1 # yet annother comment +16 | | ] +17 | | ] # once + | |_^ RUF041 +18 | +19 | # Ensure issue is only raised once, even on nested literals + | + = help: Replace with flattened `Literal` + +ℹ Unsafe fix +9 9 | Literal[1, Literal[1], Literal[1]] +10 10 | Literal[1, Literal[2], Literal[2]] +11 11 | t.Literal[1, t.Literal[2, t.Literal[1]]] +12 |-Literal[ +13 |- 1, # comment 1 +14 |- Literal[ # another comment +15 |- 1 # yet annother comment +16 |- ] +17 |-] # once + 12 |+Literal[1, 1] # once +18 13 | +19 14 | # Ensure issue is only raised once, even on nested literals +20 15 | MyType = Literal["foo", Literal[True, False, True], "bar"] + +RUF041.pyi:20:10: RUF041 [*] Unnecessary nested `Literal` + | +19 | # Ensure issue is only raised once, even on nested literals +20 | MyType = Literal["foo", Literal[True, False, True], "bar"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +21 | +22 | # nested literals, all equivalent to `Literal[1]` + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +17 17 | ] # once +18 18 | +19 19 | # Ensure issue is only raised once, even on nested literals +20 |-MyType = Literal["foo", Literal[True, False, True], "bar"] + 20 |+MyType = Literal["foo", True, False, True, "bar"] +21 21 | +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] + +RUF041.pyi:23:1: RUF041 [*] Unnecessary nested `Literal` + | +22 | # nested literals, all equivalent to `Literal[1]` +23 | Literal[Literal[1]] + | ^^^^^^^^^^^^^^^^^^^ RUF041 +24 | Literal[Literal[Literal[1], Literal[1]]] +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +20 20 | MyType = Literal["foo", Literal[True, False, True], "bar"] +21 21 | +22 22 | # nested literals, all equivalent to `Literal[1]` +23 |-Literal[Literal[1]] + 23 |+Literal[1] +24 24 | Literal[Literal[Literal[1], Literal[1]]] +25 25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] +26 26 | + +RUF041.pyi:24:1: RUF041 [*] Unnecessary nested `Literal` + | +22 | # nested literals, all equivalent to `Literal[1]` +23 | Literal[Literal[1]] +24 | Literal[Literal[Literal[1], Literal[1]]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +21 21 | +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] +24 |-Literal[Literal[Literal[1], Literal[1]]] + 24 |+Literal[1, 1] +25 25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] +26 26 | +27 27 | # OK + +RUF041.pyi:24:9: RUF041 [*] Unnecessary nested `Literal` + | +22 | # nested literals, all equivalent to `Literal[1]` +23 | Literal[Literal[1]] +24 | Literal[Literal[Literal[1], Literal[1]]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +21 21 | +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] +24 |-Literal[Literal[Literal[1], Literal[1]]] + 24 |+Literal[Literal[1, 1]] +25 25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] +26 26 | +27 27 | # OK + +RUF041.pyi:25:1: RUF041 [*] Unnecessary nested `Literal` + | +23 | Literal[Literal[1]] +24 | Literal[Literal[Literal[1], Literal[1]]] +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ RUF041 +26 | +27 | # OK + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] +24 24 | Literal[Literal[Literal[1], Literal[1]]] +25 |-Literal[Literal[1], Literal[Literal[Literal[1]]]] + 25 |+Literal[1, 1] +26 26 | +27 27 | # OK +28 28 | x: Literal[True, False, True, False] + +RUF041.pyi:25:29: RUF041 [*] Unnecessary nested `Literal` + | +23 | Literal[Literal[1]] +24 | Literal[Literal[Literal[1], Literal[1]]] +25 | Literal[Literal[1], Literal[Literal[Literal[1]]]] + | ^^^^^^^^^^^^^^^^^^^ RUF041 +26 | +27 | # OK + | + = help: Replace with flattened `Literal` + +ℹ Safe fix +22 22 | # nested literals, all equivalent to `Literal[1]` +23 23 | Literal[Literal[1]] +24 24 | Literal[Literal[Literal[1], Literal[1]]] +25 |-Literal[Literal[1], Literal[Literal[Literal[1]]]] + 25 |+Literal[Literal[1], Literal[Literal[1]]] +26 26 | +27 27 | # OK +28 28 | x: Literal[True, False, True, False] diff --git a/ruff.schema.json b/ruff.schema.json index 2ba04db4e1..8b4c92ef4b 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3841,6 +3841,7 @@ "RUF039", "RUF04", "RUF040", + "RUF041", "RUF048", "RUF1", "RUF10",