From 1283c416842decf3aaeec693a2fda597f92b0671 Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Wed, 4 Dec 2024 02:38:48 +0000 Subject: [PATCH 1/2] [`ruff`] Empty branches (`RUF050`) --- .../test/fixtures/ruff/RUF050_for.py | 43 ++ .../resources/test/fixtures/ruff/RUF050_if.py | 175 +++++ .../test/fixtures/ruff/RUF050_try.py | 359 ++++++++++ .../src/checkers/ast/analyze/statement.rs | 9 + crates/ruff_linter/src/codes.rs | 1 + .../pylint/rules/useless_else_on_loop.rs | 3 +- crates/ruff_linter/src/rules/ruff/mod.rs | 3 + .../src/rules/ruff/rules/empty_branch.rs | 266 +++++++ .../ruff_linter/src/rules/ruff/rules/mod.rs | 2 + ..._tests__preview__RUF050_RUF050_for.py.snap | 76 ++ ...__tests__preview__RUF050_RUF050_if.py.snap | 400 +++++++++++ ..._tests__preview__RUF050_RUF050_try.py.snap | 665 ++++++++++++++++++ crates/ruff_python_ast/src/identifier.rs | 46 +- .../tests/identifier.rs | 63 +- ruff.schema.json | 3 +- 15 files changed, 2098 insertions(+), 16 deletions(-) create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF050_for.py create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF050_if.py create mode 100644 crates/ruff_linter/resources/test/fixtures/ruff/RUF050_try.py create mode 100644 crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_for.py.snap create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_if.py.snap create mode 100644 crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_try.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_for.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_for.py new file mode 100644 index 0000000000000..06e8b84a752ab --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_for.py @@ -0,0 +1,43 @@ +### +# E = Empty +# N = Not empty +# O = Omitted +### + + +# Unsafe fix: Remove statement +for E in O: + ... + + +# Unsafe fix: Remove statement +for E in E: + pass +else: + ... + + +# No fix +for E in N: + pass +else: + print() + + +# No error +for N in O: + print() + + +# Safe fix: Remove `else` branch +for N in E: + print() +else: + ... + + +# No error +for N in N: + print() +else: + print() diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_if.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_if.py new file mode 100644 index 0000000000000..26848bffe3583 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_if.py @@ -0,0 +1,175 @@ +### +# E = Empty +# N = Not empty +# O = Omitted +### + + +# Unsafe fix: Remove statement +if EOO: + pass + + +# Unsafe fix: Remove statement +if EOE: + ... +else: + pass + + +# No fix +if EON: + ... +else: + print() + + +# Unsafe fix: Remove statement +if EEO: + pass +elif _: + ... + + +# Unsafe fix: Remove statement +if EEE: + pass +elif _: + ... +else: + pass + + +# No fix +# Display-only fix: Remove `elif` branch +if EEN: + pass +elif _: + ... +else: + print() + + +# No fix +if ENO: + pass +elif _: + print() + + +# No fix +# Safe fix: Remove `else` branch +if ENE: + ... +elif _: + print() +else: + pass + + +# No fix +if ENN: + ... +elif _: + print() +else: + print() + + +# No error +if NOO: + print() + + +# Safe fix: Remove `else` branch +if NOE: + print() +else: + pass + + +# No error +if NON: + print() +else: + print() + + +# Unsafe fix: Remove `elif` branch +if NEO: + print() +elif _: + ... + + +# Display-only fix: Remove `elif` branch +# Safe fix: Remove `else` branch +if NEE: + print() +elif _: + pass +else: + ... + + +# Display-only fix: Remove `elif` branch +if NEN: + print() +elif _: + pass +else: + print() + + +# No error +if NNO: + print() +elif _: + print() + + +# Safe fix: Remove `else` branch +if NNE: + print() +elif _: + print() +else: + ... + + +# No error +if NNN: + print() +elif _: + print() +else: + print() + + +##### + + +# Display-only fix: Remove `elif` branch +# Unsafe fix: Remove `elif` branch +if NEE_: + print() +elif _: + pass +elif _: + ... + + +# Unsafe fix: Remove `elif` branch +if NEO_: + print() +# Lorem ipsum +elif _: + ... + + +# Unsafe fix: Remove `else` branch +if NOE_: + print() +else: + # Lorem ipsum + pass diff --git a/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_try.py b/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_try.py new file mode 100644 index 0000000000000..00eff3d2c19a9 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/ruff/RUF050_try.py @@ -0,0 +1,359 @@ +### +# E = Empty +# N = Not empty +# O = Omitted +### + + +# Safe fix: Remove statement +try: + pass +except: + ... + + +# Safe fix: Remove statement +try: + pass +except: + ... +finally: + pass + + +# No fix +try: + ... +except: + pass +finally: + print() + + +# Safe fix: Remove statement +try: + ... +except: + pass +else: + ... + + +# Safe fix: Remove statement +try: + pass +except: + ... +else: + pass +finally: + ... + + +# No fix +# Safe fix: Remove `else` branch +try: + pass +except: + ... +else: + pass +finally: + print() + + +# No fix +try: + ... +except: + pass +else: + print() + + +# No fix +# Safe fix: Remove `finally` branch +try: + ... +except: + pass +else: + print() +finally: + ... + + +# No fix +try: + pass +except: + ... +else: + print() +finally: + print() + + +# Safe fix: Remove statement +try: + pass +except: + print() + + +# Safe fix: Remove statement +try: + ... +except: + print() +finally: + pass + + +# No fix +try: + ... +except: + print() +finally: + print() + + +# Safe fix: Remove statement +try: + pass +except: + print() +else: + ... + + +# Safe fix: Remove statement +try: + pass +except: + print() +else: + ... +finally: + pass + + +# No fix +# Safe fix: Remove `else` branch +try: + ... +except: + print() +else: + pass +finally: + print() + + +# No fix +try: + ... +except: + print() +else: + print() + + +# No fix +# Safe fix: Remove `finally` branch +try: + pass +except: + print() +else: + print() +finally: + ... + + +# No fix +try: + pass +except: + print() +else: + print() +finally: + print() + + +# Safe fix: Remove `finally` branch +try: + print() +except: + ... + + +# Safe fix: Remove `finally` branch +try: + print() +except: + pass +finally: + ... + + +# No error +try: + print() +except: + pass +finally: + print() + + +# Safe fix: Remove `else` branch +try: + print() +except: + ... +else: + pass + + +# Safe fix: Remove `else` branch +# Safe fix: Remove `finally` branch +try: + print() +except: + ... +else: + pass +finally: + ... + + +# Safe fix: Remove `else` branch +try: + print() +except: + pass +else: + ... +finally: + print() + + +# No error +try: + print() +except: + pass +else: + print() + + +# Safe fix: Remove `finally` branch +try: + print() +except: + ... +else: + print() +finally: + pass + + +# No error +try: + print() +except: + ... +else: + print() +finally: + print() + + +# No error +try: + print() +except: + print() + + +# Safe fix: Remove `finally` branch +try: + print() +except: + print() +finally: + pass + + +# No error +try: + print() +except: + print() +finally: + print() + + +# Safe fix: Remove `else` branch +try: + print() +except: + print() +else: + ... + + +# Safe fix: Remove `else` branch +# Safe fix: Remove `finally` branch +try: + print() +except: + print() +else: + pass +finally: + ... + + +# Safe fix: Remove `else` branch +try: + print() +except: + print() +else: + pass +finally: + print() + + +# No error +try: + print() +except: + print() +else: + print() + + +# Safe fix: Remove `finally` branch +try: + print() +except: + print() +else: + print() +finally: + ... + + +# No error +try: + print() +except: + print() +else: + print() +finally: + print() diff --git a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs index 61e1bf43a6356..42c111cc19561 100644 --- a/crates/ruff_linter/src/checkers/ast/analyze/statement.rs +++ b/crates/ruff_linter/src/checkers/ast/analyze/statement.rs @@ -1235,6 +1235,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { } } } + if checker.enabled(Rule::EmptyBranch) { + ruff::rules::empty_branch_if(checker, stmt); + } } Stmt::Assert( assert_stmt @ ast::StmtAssert { @@ -1429,6 +1432,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { refurb::rules::for_loop_set_mutations(checker, for_stmt); } } + if checker.enabled(Rule::EmptyBranch) { + ruff::rules::empty_branch_for(checker, stmt); + } } Stmt::Try(ast::StmtTry { body, @@ -1503,6 +1509,9 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) { if checker.enabled(Rule::ErrorInsteadOfException) { tryceratops::rules::error_instead_of_exception(checker, handlers); } + if checker.enabled(Rule::EmptyBranch) { + ruff::rules::empty_branch_try(checker, stmt); + } } Stmt::Assign(assign @ ast::StmtAssign { targets, value, .. }) => { if checker.enabled(Rule::SelfOrClsAssignment) { diff --git a/crates/ruff_linter/src/codes.rs b/crates/ruff_linter/src/codes.rs index d35bf82bbde2e..323929743a41c 100644 --- a/crates/ruff_linter/src/codes.rs +++ b/crates/ruff_linter/src/codes.rs @@ -984,6 +984,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> { (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, "050") => (RuleGroup::Preview, rules::ruff::rules::EmptyBranch), (Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable), (Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression), (Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA), diff --git a/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs b/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs index 19ebcf1dfebf6..386b7d7aa3fde 100644 --- a/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs +++ b/crates/ruff_linter/src/rules/pylint/rules/useless_else_on_loop.rs @@ -73,7 +73,8 @@ pub(crate) fn useless_else_on_loop( return; } - let else_range = identifier::else_(stmt, checker.locator().contents()).expect("else clause"); + let else_range = + identifier::else_loop(stmt, checker.locator().contents()).expect("else clause"); let mut diagnostic = Diagnostic::new(UselessElseOnLoop, else_range); diagnostic.try_set_fix(|| { diff --git a/crates/ruff_linter/src/rules/ruff/mod.rs b/crates/ruff_linter/src/rules/ruff/mod.rs index 14d6f3e121a35..56a58f6fc0d1e 100644 --- a/crates/ruff_linter/src/rules/ruff/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/mod.rs @@ -413,6 +413,9 @@ mod tests { #[test_case(Rule::UnrawRePattern, Path::new("RUF039_concat.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_0.py"))] #[test_case(Rule::UnnecessaryRegularExpression, Path::new("RUF055_1.py"))] + #[test_case(Rule::EmptyBranch, Path::new("RUF050_if.py"))] + #[test_case(Rule::EmptyBranch, Path::new("RUF050_for.py"))] + #[test_case(Rule::EmptyBranch, Path::new("RUF050_try.py"))] fn preview_rules(rule_code: Rule, path: &Path) -> Result<()> { let snapshot = format!( "preview__{}_{}", diff --git a/crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs b/crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs new file mode 100644 index 0000000000000..f1e0562a14eca --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs @@ -0,0 +1,266 @@ +use crate::checkers::ast::Checker; +use crate::fix::edits::delete_stmt; +use ruff_diagnostics::{Applicability, Diagnostic, Edit, Fix, FixAvailability, Violation}; +use ruff_macros::{derive_message_formats, ViolationMetadata}; +use ruff_python_ast::{identifier, Expr, Stmt, StmtExpr, StmtFor}; +use ruff_text_size::{Ranged, TextRange}; + +/// ## What it does +/// Checks for branches whose bodies contain only `pass` and `...` statements. +/// +/// ## Why is this bad? +/// Such a branch is unnecessary. +/// +/// ## Example +/// +/// ```python +/// if foo: +/// bar() +/// else: +/// pass +/// ``` +/// +/// ```python +/// if foo: +/// bar() +/// ``` +#[derive(ViolationMetadata)] +pub(crate) struct EmptyBranch { + fixable: bool, +} + +impl Violation for EmptyBranch { + const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes; + + #[derive_message_formats] + fn message(&self) -> String { + "Empty code branch".to_string() + } + + fn fix_title(&self) -> Option { + if self.fixable { + Some("Remove branch".to_string()) + } else { + Some("Refactor this branch".to_string()) + } + } +} + +/// RUF050 +fn empty_branch(checker: &mut Checker, range: TextRange, fix: Option) { + let fixable = fix.is_some(); + let mut diagnostic = Diagnostic::new(EmptyBranch { fixable }, range); + + diagnostic.try_set_optional_fix(|| Ok(fix)); + checker.diagnostics.push(diagnostic) +} + +/// RUF050: `if`-`elif`-`else` +pub(crate) fn empty_branch_if(checker: &mut Checker, stmt: &Stmt) { + let Stmt::If(stmt_if) = stmt else { + return; + }; + let (body, elif_else_clauses) = (&stmt_if.body, &stmt_if.elif_else_clauses); + + let (indexer, locator, semantic) = (checker.indexer(), checker.locator(), checker.semantic()); + + let Some(if_body_end) = body.last().map(Ranged::end) else { + return; + }; + + if body_is_empty(body) { + let parent_stmt = semantic.current_statement_parent(); + let no_elif_else = elif_else_clauses.iter().all(|it| body_is_empty(&it.body)); + + let range = if no_elif_else { + stmt.range() + } else { + TextRange::new(stmt.start(), if_body_end) + }; + + let edit = no_elif_else.then(|| delete_stmt(stmt, parent_stmt, locator, indexer)); + let fix = edit.map(|it| make_fix(checker, it, Applicability::Unsafe)); + + empty_branch(checker, range, fix); + + if no_elif_else { + return; + } + } + + for (index, clause) in elif_else_clauses.iter().enumerate() { + if !body_is_empty(&clause.body) { + continue; + } + + let clause_is_else = clause.test.is_none(); + let clause_is_last = index == elif_else_clauses.len() - 1; + + let base_applicability = match (clause_is_else, clause_is_last) { + (true, _) => Applicability::Safe, + (_, true) => Applicability::Unsafe, + (_, false) => Applicability::DisplayOnly, + }; + + let previous_clause = elif_else_clauses[..index].last(); + let previous_clause_end = previous_clause.map(Ranged::end).unwrap_or(if_body_end); + + let edit = Edit::deletion(previous_clause_end, clause.end()); + let fix = make_fix(checker, edit, base_applicability); + + empty_branch(checker, clause.range, Some(fix)); + } +} + +/// RUF050: `for`-`else` +pub(crate) fn empty_branch_for(checker: &mut Checker, stmt: &Stmt) { + let Stmt::For(StmtFor { body, orelse, .. }) = stmt else { + return; + }; + + let (indexer, locator, semantic) = (checker.indexer(), checker.locator(), checker.semantic()); + + let for_body_is_empty = body_is_empty(body); + let else_body_is_empty = body_is_empty(orelse); + + if for_body_is_empty { + let parent_stmt = semantic.current_statement_parent(); + let no_else_branch = orelse.is_empty() || else_body_is_empty; + + let edit = no_else_branch.then(|| delete_stmt(stmt, parent_stmt, locator, indexer)); + let fix = edit.map(|it| make_fix(checker, it, Applicability::Unsafe)); + + empty_branch(checker, stmt.range(), fix); + + if no_else_branch { + return; + } + } + + let Some(for_body_end) = body.last().map(Ranged::end) else { + return; + }; + + if else_body_is_empty { + let Some(else_keyword_range) = identifier::else_loop(stmt, locator.contents()) else { + return; + }; + let Some(else_branch_end) = orelse.last().map(Ranged::end) else { + return; + }; + let range = TextRange::new(else_keyword_range.start(), else_branch_end); + + let edit = Edit::deletion(for_body_end, stmt.end()); + let fix = make_fix(checker, edit, Applicability::Safe); + + empty_branch(checker, range, Some(fix)); + } +} + +/// RUF050: `try`-`except`-`else`-`finally` +pub(crate) fn empty_branch_try(checker: &mut Checker, stmt: &Stmt) { + let Stmt::Try(stmt_try) = stmt else { + return; + }; + let (body, handlers, orelse, finalbody) = ( + &stmt_try.body, + &stmt_try.handlers, + &stmt_try.orelse, + &stmt_try.finalbody, + ); + + let (indexer, locator, semantic) = (checker.indexer(), checker.locator(), checker.semantic()); + + let try_body_is_empty = body_is_empty(body); + let else_body_is_empty = body_is_empty(orelse); + let finally_body_is_empty = body_is_empty(finalbody); + + let Some(try_body_end) = body.last().map(Ranged::end) else { + return; + }; + let last_handler_end = handlers.last().map(Ranged::end); + let else_branch_end = orelse.last().map(Ranged::end); + + if try_body_is_empty { + let parent_stmt = semantic.current_statement_parent(); + let no_else = orelse.is_empty() || else_body_is_empty; + let no_finally = finalbody.is_empty() || finally_body_is_empty; + let no_else_no_finally = no_else && no_finally; + + let range = if no_else_no_finally { + stmt.range() + } else { + TextRange::new(stmt_try.start(), try_body_end) + }; + + let edit = no_else_no_finally.then(|| delete_stmt(stmt, parent_stmt, locator, indexer)); + let fix = edit.map(|it| make_fix(checker, it, Applicability::Safe)); + + empty_branch(checker, range, fix); + + if no_else_no_finally { + return; + } + } + + if body_is_empty(orelse) { + let Some(else_keyword_range) = identifier::else_try(stmt, locator.contents()) else { + return; + }; + let else_branch_end = else_branch_end.unwrap(); + let range = TextRange::new(else_keyword_range.start(), else_branch_end); + + let edit = Edit::deletion(last_handler_end.unwrap_or(try_body_end), else_branch_end); + let fix = make_fix(checker, edit, Applicability::Safe); + + empty_branch(checker, range, Some(fix)); + } + + if body_is_empty(finalbody) { + let Some(finally_keyword_range) = identifier::finally(stmt, locator.contents()) else { + return; + }; + let Some(finally_branch_end) = finalbody.last().map(Ranged::end) else { + return; + }; + let range = TextRange::new(finally_keyword_range.start(), finally_branch_end); + + let last_branch_end = else_branch_end.or(last_handler_end).unwrap_or(try_body_end); + let edit = Edit::deletion(last_branch_end, finally_branch_end); + let fix = make_fix(checker, edit, Applicability::Safe); + + empty_branch(checker, range, Some(fix)); + } +} + +/// Whether `body` contains only `pass` or `...` statement. +fn body_is_empty(body: &[Stmt]) -> bool { + if body.len() == 0 { + return false; + } + + body.iter().all(|stmt| match stmt { + Stmt::Pass(..) => true, + Stmt::Expr(StmtExpr { value, .. }) => matches!(value.as_ref(), Expr::EllipsisLiteral(..)), + _ => false, + }) +} + +fn make_fix(checker: &Checker, edit: Edit, base_applicability: Applicability) -> Fix { + let source = checker.source(); + + let min_applicability = if checker.comment_ranges().has_comments(&edit, source) { + Applicability::Unsafe + } else { + Applicability::Safe + }; + let applicability = match (base_applicability, min_applicability) { + (Applicability::DisplayOnly, _) | (_, Applicability::DisplayOnly) => { + Applicability::DisplayOnly + } + (Applicability::Unsafe, _) | (_, Applicability::Unsafe) => Applicability::Unsafe, + _ => Applicability::Safe, + }; + + Fix::applicable_edit(edit, applicability) +} diff --git a/crates/ruff_linter/src/rules/ruff/rules/mod.rs b/crates/ruff_linter/src/rules/ruff/rules/mod.rs index 69f92b8da2bab..37df4f8d14734 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/mod.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/mod.rs @@ -5,6 +5,7 @@ pub(crate) use asyncio_dangling_task::*; pub(crate) use collection_literal_concatenation::*; pub(crate) use decimal_from_float_literal::*; pub(crate) use default_factory_kwarg::*; +pub(crate) use empty_branch::*; pub(crate) use explicit_f_string_type_conversion::*; pub(crate) use function_call_in_dataclass_default::*; pub(crate) use implicit_optional::*; @@ -50,6 +51,7 @@ mod collection_literal_concatenation; mod confusables; mod decimal_from_float_literal; mod default_factory_kwarg; +mod empty_branch; mod explicit_f_string_type_conversion; mod function_call_in_dataclass_default; mod helpers; diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_for.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_for.py.snap new file mode 100644 index 0000000000000..43f08c7450fe1 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_for.py.snap @@ -0,0 +1,76 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +snapshot_kind: text +--- +RUF050_for.py:9:1: RUF050 [*] Empty code branch + | + 8 | # Unsafe fix: Remove statement + 9 | / for E in O: +10 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +6 6 | +7 7 | +8 8 | # Unsafe fix: Remove statement +9 |-for E in O: +10 |- ... +11 9 | +12 10 | +13 11 | # Unsafe fix: Remove statement + +RUF050_for.py:14:1: RUF050 [*] Empty code branch + | +13 | # Unsafe fix: Remove statement +14 | / for E in E: +15 | | pass +16 | | else: +17 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +11 11 | +12 12 | +13 13 | # Unsafe fix: Remove statement +14 |-for E in E: +15 |- pass +16 |-else: +17 |- ... +18 14 | +19 15 | +20 16 | # No fix + +RUF050_for.py:21:1: RUF050 Empty code branch + | +20 | # No fix +21 | / for E in N: +22 | | pass +23 | | else: +24 | | print() + | |___________^ RUF050 + | + = help: Refactor this branch + +RUF050_for.py:35:1: RUF050 [*] Empty code branch + | +33 | for N in E: +34 | print() +35 | / else: +36 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +32 32 | # Safe fix: Remove `else` branch +33 33 | for N in E: +34 34 | print() +35 |-else: +36 |- ... +37 35 | +38 36 | +39 37 | # No error diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_if.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_if.py.snap new file mode 100644 index 0000000000000..8385d7b057af8 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_if.py.snap @@ -0,0 +1,400 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +snapshot_kind: text +--- +RUF050_if.py:9:1: RUF050 [*] Empty code branch + | + 8 | # Unsafe fix: Remove statement + 9 | / if EOO: +10 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +6 6 | +7 7 | +8 8 | # Unsafe fix: Remove statement +9 |-if EOO: +10 |- pass +11 9 | +12 10 | +13 11 | # Unsafe fix: Remove statement + +RUF050_if.py:14:1: RUF050 [*] Empty code branch + | +13 | # Unsafe fix: Remove statement +14 | / if EOE: +15 | | ... +16 | | else: +17 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +11 11 | +12 12 | +13 13 | # Unsafe fix: Remove statement +14 |-if EOE: +15 |- ... +16 |-else: +17 |- pass +18 14 | +19 15 | +20 16 | # No fix + +RUF050_if.py:21:1: RUF050 Empty code branch + | +20 | # No fix +21 | / if EON: +22 | | ... + | |_______^ RUF050 +23 | else: +24 | print() + | + = help: Refactor this branch + +RUF050_if.py:28:1: RUF050 [*] Empty code branch + | +27 | # Unsafe fix: Remove statement +28 | / if EEO: +29 | | pass +30 | | elif _: +31 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +25 25 | +26 26 | +27 27 | # Unsafe fix: Remove statement +28 |-if EEO: +29 |- pass +30 |-elif _: +31 |- ... +32 28 | +33 29 | +34 30 | # Unsafe fix: Remove statement + +RUF050_if.py:35:1: RUF050 [*] Empty code branch + | +34 | # Unsafe fix: Remove statement +35 | / if EEE: +36 | | pass +37 | | elif _: +38 | | ... +39 | | else: +40 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +32 32 | +33 33 | +34 34 | # Unsafe fix: Remove statement +35 |-if EEE: +36 |- pass +37 |-elif _: +38 |- ... +39 |-else: +40 |- pass +41 35 | +42 36 | +43 37 | # No fix + +RUF050_if.py:45:1: RUF050 Empty code branch + | +43 | # No fix +44 | # Display-only fix: Remove `elif` branch +45 | / if EEN: +46 | | pass + | |________^ RUF050 +47 | elif _: +48 | ... + | + = help: Refactor this branch + +RUF050_if.py:47:1: RUF050 Empty code branch + | +45 | if EEN: +46 | pass +47 | / elif _: +48 | | ... + | |_______^ RUF050 +49 | else: +50 | print() + | + = help: Remove branch + +ℹ Display-only fix +44 44 | # Display-only fix: Remove `elif` branch +45 45 | if EEN: +46 46 | pass +47 |-elif _: +48 |- ... +49 47 | else: +50 48 | print() +51 49 | + +RUF050_if.py:54:1: RUF050 Empty code branch + | +53 | # No fix +54 | / if ENO: +55 | | pass + | |________^ RUF050 +56 | elif _: +57 | print() + | + = help: Refactor this branch + +RUF050_if.py:62:1: RUF050 Empty code branch + | +60 | # No fix +61 | # Safe fix: Remove `else` branch +62 | / if ENE: +63 | | ... + | |_______^ RUF050 +64 | elif _: +65 | print() + | + = help: Refactor this branch + +RUF050_if.py:66:1: RUF050 [*] Empty code branch + | +64 | elif _: +65 | print() +66 | / else: +67 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +63 63 | ... +64 64 | elif _: +65 65 | print() +66 |-else: +67 |- pass +68 66 | +69 67 | +70 68 | # No fix + +RUF050_if.py:71:1: RUF050 Empty code branch + | +70 | # No fix +71 | / if ENN: +72 | | ... + | |_______^ RUF050 +73 | elif _: +74 | print() + | + = help: Refactor this branch + +RUF050_if.py:87:1: RUF050 [*] Empty code branch + | +85 | if NOE: +86 | print() +87 | / else: +88 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +84 84 | # Safe fix: Remove `else` branch +85 85 | if NOE: +86 86 | print() +87 |-else: +88 |- pass +89 87 | +90 88 | +91 89 | # No error + +RUF050_if.py:101:1: RUF050 [*] Empty code branch + | + 99 | if NEO: +100 | print() +101 | / elif _: +102 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +98 98 | # Unsafe fix: Remove `elif` branch +99 99 | if NEO: +100 100 | print() +101 |-elif _: +102 |- ... +103 101 | +104 102 | +105 103 | # Display-only fix: Remove `elif` branch + +RUF050_if.py:109:1: RUF050 Empty code branch + | +107 | if NEE: +108 | print() +109 | / elif _: +110 | | pass + | |________^ RUF050 +111 | else: +112 | ... + | + = help: Remove branch + +ℹ Display-only fix +106 106 | # Safe fix: Remove `else` branch +107 107 | if NEE: +108 108 | print() +109 |-elif _: +110 |- pass +111 109 | else: +112 110 | ... +113 111 | + +RUF050_if.py:111:1: RUF050 [*] Empty code branch + | +109 | elif _: +110 | pass +111 | / else: +112 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +108 108 | print() +109 109 | elif _: +110 110 | pass +111 |-else: +112 |- ... +113 111 | +114 112 | +115 113 | # Display-only fix: Remove `elif` branch + +RUF050_if.py:118:1: RUF050 Empty code branch + | +116 | if NEN: +117 | print() +118 | / elif _: +119 | | pass + | |________^ RUF050 +120 | else: +121 | print() + | + = help: Remove branch + +ℹ Display-only fix +115 115 | # Display-only fix: Remove `elif` branch +116 116 | if NEN: +117 117 | print() +118 |-elif _: +119 |- pass +120 118 | else: +121 119 | print() +122 120 | + +RUF050_if.py:136:1: RUF050 [*] Empty code branch + | +134 | elif _: +135 | print() +136 | / else: +137 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +133 133 | print() +134 134 | elif _: +135 135 | print() +136 |-else: +137 |- ... +138 136 | +139 137 | +140 138 | # No error + +RUF050_if.py:156:1: RUF050 Empty code branch + | +154 | if NEE_: +155 | print() +156 | / elif _: +157 | | pass + | |________^ RUF050 +158 | elif _: +159 | ... + | + = help: Remove branch + +ℹ Display-only fix +154 154 | if NEE_: +155 155 | print() +156 156 | elif _: +157 |- pass +158 |-elif _: +159 157 | ... +160 158 | +161 159 | + +RUF050_if.py:158:1: RUF050 [*] Empty code branch + | +156 | elif _: +157 | pass +158 | / elif _: +159 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +155 155 | print() +156 156 | elif _: +157 157 | pass +158 |-elif _: +159 |- ... +160 158 | +161 159 | +162 160 | # Unsafe fix: Remove `elif` branch + +RUF050_if.py:166:1: RUF050 [*] Empty code branch + | +164 | print() +165 | # Lorem ipsum +166 | / elif _: +167 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +162 162 | # Unsafe fix: Remove `elif` branch +163 163 | if NEO_: +164 164 | print() +165 |-# Lorem ipsum +166 |-elif _: +167 |- ... +168 165 | +169 166 | +170 167 | # Unsafe fix: Remove `else` branch + +RUF050_if.py:173:1: RUF050 [*] Empty code branch + | +171 | if NOE_: +172 | print() +173 | / else: +174 | | # Lorem ipsum +175 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Unsafe fix +170 170 | # Unsafe fix: Remove `else` branch +171 171 | if NOE_: +172 172 | print() +173 |-else: +174 |- # Lorem ipsum +175 |- pass diff --git a/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_try.py.snap b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_try.py.snap new file mode 100644 index 0000000000000..6e6f47d68db82 --- /dev/null +++ b/crates/ruff_linter/src/rules/ruff/snapshots/ruff_linter__rules__ruff__tests__preview__RUF050_RUF050_try.py.snap @@ -0,0 +1,665 @@ +--- +source: crates/ruff_linter/src/rules/ruff/mod.rs +snapshot_kind: text +--- +RUF050_try.py:9:1: RUF050 [*] Empty code branch + | + 8 | # Safe fix: Remove statement + 9 | / try: +10 | | pass +11 | | except: +12 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +6 6 | +7 7 | +8 8 | # Safe fix: Remove statement +9 |-try: +10 |- pass +11 |-except: +12 |- ... +13 9 | +14 10 | +15 11 | # Safe fix: Remove statement + +RUF050_try.py:16:1: RUF050 [*] Empty code branch + | +15 | # Safe fix: Remove statement +16 | / try: +17 | | pass +18 | | except: +19 | | ... +20 | | finally: +21 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +13 13 | +14 14 | +15 15 | # Safe fix: Remove statement +16 |-try: +17 |- pass +18 |-except: +19 |- ... +20 |-finally: +21 |- pass +22 16 | +23 17 | +24 18 | # No fix + +RUF050_try.py:25:1: RUF050 Empty code branch + | +24 | # No fix +25 | / try: +26 | | ... + | |_______^ RUF050 +27 | except: +28 | pass + | + = help: Refactor this branch + +RUF050_try.py:34:1: RUF050 [*] Empty code branch + | +33 | # Safe fix: Remove statement +34 | / try: +35 | | ... +36 | | except: +37 | | pass +38 | | else: +39 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +31 31 | +32 32 | +33 33 | # Safe fix: Remove statement +34 |-try: +35 |- ... +36 |-except: +37 |- pass +38 |-else: +39 |- ... +40 34 | +41 35 | +42 36 | # Safe fix: Remove statement + +RUF050_try.py:43:1: RUF050 [*] Empty code branch + | +42 | # Safe fix: Remove statement +43 | / try: +44 | | pass +45 | | except: +46 | | ... +47 | | else: +48 | | pass +49 | | finally: +50 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +40 40 | +41 41 | +42 42 | # Safe fix: Remove statement +43 |-try: +44 |- pass +45 |-except: +46 |- ... +47 |-else: +48 |- pass +49 |-finally: +50 |- ... +51 43 | +52 44 | +53 45 | # No fix + +RUF050_try.py:55:1: RUF050 Empty code branch + | +53 | # No fix +54 | # Safe fix: Remove `else` branch +55 | / try: +56 | | pass + | |________^ RUF050 +57 | except: +58 | ... + | + = help: Refactor this branch + +RUF050_try.py:59:1: RUF050 [*] Empty code branch + | +57 | except: +58 | ... +59 | / else: +60 | | pass + | |________^ RUF050 +61 | finally: +62 | print() + | + = help: Remove branch + +ℹ Safe fix +56 56 | pass +57 57 | except: +58 58 | ... +59 |-else: +60 |- pass +61 59 | finally: +62 60 | print() +63 61 | + +RUF050_try.py:66:1: RUF050 Empty code branch + | +65 | # No fix +66 | / try: +67 | | ... + | |_______^ RUF050 +68 | except: +69 | pass + | + = help: Refactor this branch + +RUF050_try.py:76:1: RUF050 Empty code branch + | +74 | # No fix +75 | # Safe fix: Remove `finally` branch +76 | / try: +77 | | ... + | |_______^ RUF050 +78 | except: +79 | pass + | + = help: Refactor this branch + +RUF050_try.py:82:1: RUF050 [*] Empty code branch + | +80 | else: +81 | print() +82 | / finally: +83 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +79 79 | pass +80 80 | else: +81 81 | print() +82 |-finally: +83 |- ... +84 82 | +85 83 | +86 84 | # No fix + +RUF050_try.py:87:1: RUF050 Empty code branch + | +86 | # No fix +87 | / try: +88 | | pass + | |________^ RUF050 +89 | except: +90 | ... + | + = help: Refactor this branch + +RUF050_try.py:98:1: RUF050 [*] Empty code branch + | + 97 | # Safe fix: Remove statement + 98 | / try: + 99 | | pass +100 | | except: +101 | | print() + | |___________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +95 95 | +96 96 | +97 97 | # Safe fix: Remove statement +98 |-try: +99 |- pass +100 |-except: +101 |- print() +102 98 | +103 99 | +104 100 | # Safe fix: Remove statement + +RUF050_try.py:105:1: RUF050 [*] Empty code branch + | +104 | # Safe fix: Remove statement +105 | / try: +106 | | ... +107 | | except: +108 | | print() +109 | | finally: +110 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +102 102 | +103 103 | +104 104 | # Safe fix: Remove statement +105 |-try: +106 |- ... +107 |-except: +108 |- print() +109 |-finally: +110 |- pass +111 105 | +112 106 | +113 107 | # No fix + +RUF050_try.py:114:1: RUF050 Empty code branch + | +113 | # No fix +114 | / try: +115 | | ... + | |_______^ RUF050 +116 | except: +117 | print() + | + = help: Refactor this branch + +RUF050_try.py:123:1: RUF050 [*] Empty code branch + | +122 | # Safe fix: Remove statement +123 | / try: +124 | | pass +125 | | except: +126 | | print() +127 | | else: +128 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +120 120 | +121 121 | +122 122 | # Safe fix: Remove statement +123 |-try: +124 |- pass +125 |-except: +126 |- print() +127 |-else: +128 |- ... +129 123 | +130 124 | +131 125 | # Safe fix: Remove statement + +RUF050_try.py:132:1: RUF050 [*] Empty code branch + | +131 | # Safe fix: Remove statement +132 | / try: +133 | | pass +134 | | except: +135 | | print() +136 | | else: +137 | | ... +138 | | finally: +139 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +129 129 | +130 130 | +131 131 | # Safe fix: Remove statement +132 |-try: +133 |- pass +134 |-except: +135 |- print() +136 |-else: +137 |- ... +138 |-finally: +139 |- pass +140 132 | +141 133 | +142 134 | # No fix + +RUF050_try.py:144:1: RUF050 Empty code branch + | +142 | # No fix +143 | # Safe fix: Remove `else` branch +144 | / try: +145 | | ... + | |_______^ RUF050 +146 | except: +147 | print() + | + = help: Refactor this branch + +RUF050_try.py:148:1: RUF050 [*] Empty code branch + | +146 | except: +147 | print() +148 | / else: +149 | | pass + | |________^ RUF050 +150 | finally: +151 | print() + | + = help: Remove branch + +ℹ Safe fix +145 145 | ... +146 146 | except: +147 147 | print() +148 |-else: +149 |- pass +150 148 | finally: +151 149 | print() +152 150 | + +RUF050_try.py:155:1: RUF050 Empty code branch + | +154 | # No fix +155 | / try: +156 | | ... + | |_______^ RUF050 +157 | except: +158 | print() + | + = help: Refactor this branch + +RUF050_try.py:165:1: RUF050 Empty code branch + | +163 | # No fix +164 | # Safe fix: Remove `finally` branch +165 | / try: +166 | | pass + | |________^ RUF050 +167 | except: +168 | print() + | + = help: Refactor this branch + +RUF050_try.py:171:1: RUF050 [*] Empty code branch + | +169 | else: +170 | print() +171 | / finally: +172 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +168 168 | print() +169 169 | else: +170 170 | print() +171 |-finally: +172 |- ... +173 171 | +174 172 | +175 173 | # No fix + +RUF050_try.py:176:1: RUF050 Empty code branch + | +175 | # No fix +176 | / try: +177 | | pass + | |________^ RUF050 +178 | except: +179 | print() + | + = help: Refactor this branch + +RUF050_try.py:198:1: RUF050 [*] Empty code branch + | +196 | except: +197 | pass +198 | / finally: +199 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +195 195 | print() +196 196 | except: +197 197 | pass +198 |-finally: +199 |- ... +200 198 | +201 199 | +202 200 | # No error + +RUF050_try.py:216:1: RUF050 [*] Empty code branch + | +214 | except: +215 | ... +216 | / else: +217 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +213 213 | print() +214 214 | except: +215 215 | ... +216 |-else: +217 |- pass +218 216 | +219 217 | +220 218 | # Safe fix: Remove `else` branch + +RUF050_try.py:226:1: RUF050 [*] Empty code branch + | +224 | except: +225 | ... +226 | / else: +227 | | pass + | |________^ RUF050 +228 | finally: +229 | ... + | + = help: Remove branch + +ℹ Safe fix +223 223 | print() +224 224 | except: +225 225 | ... +226 |-else: +227 |- pass +228 226 | finally: +229 227 | ... +230 228 | + +RUF050_try.py:228:1: RUF050 [*] Empty code branch + | +226 | else: +227 | pass +228 | / finally: +229 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +225 225 | ... +226 226 | else: +227 227 | pass +228 |-finally: +229 |- ... +230 228 | +231 229 | +232 230 | # Safe fix: Remove `else` branch + +RUF050_try.py:237:1: RUF050 [*] Empty code branch + | +235 | except: +236 | pass +237 | / else: +238 | | ... + | |_______^ RUF050 +239 | finally: +240 | print() + | + = help: Remove branch + +ℹ Safe fix +234 234 | print() +235 235 | except: +236 236 | pass +237 |-else: +238 |- ... +239 237 | finally: +240 238 | print() +241 239 | + +RUF050_try.py:259:1: RUF050 [*] Empty code branch + | +257 | else: +258 | print() +259 | / finally: +260 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +256 256 | ... +257 257 | else: +258 258 | print() +259 |-finally: +260 |- pass +261 259 | +262 260 | +263 261 | # No error + +RUF050_try.py:286:1: RUF050 [*] Empty code branch + | +284 | except: +285 | print() +286 | / finally: +287 | | pass + | |________^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +283 283 | print() +284 284 | except: +285 285 | print() +286 |-finally: +287 |- pass +288 286 | +289 287 | +290 288 | # No error + +RUF050_try.py:304:1: RUF050 [*] Empty code branch + | +302 | except: +303 | print() +304 | / else: +305 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +301 301 | print() +302 302 | except: +303 303 | print() +304 |-else: +305 |- ... +306 304 | +307 305 | +308 306 | # Safe fix: Remove `else` branch + +RUF050_try.py:314:1: RUF050 [*] Empty code branch + | +312 | except: +313 | print() +314 | / else: +315 | | pass + | |________^ RUF050 +316 | finally: +317 | ... + | + = help: Remove branch + +ℹ Safe fix +311 311 | print() +312 312 | except: +313 313 | print() +314 |-else: +315 |- pass +316 314 | finally: +317 315 | ... +318 316 | + +RUF050_try.py:316:1: RUF050 [*] Empty code branch + | +314 | else: +315 | pass +316 | / finally: +317 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +313 313 | print() +314 314 | else: +315 315 | pass +316 |-finally: +317 |- ... +318 316 | +319 317 | +320 318 | # Safe fix: Remove `else` branch + +RUF050_try.py:325:1: RUF050 [*] Empty code branch + | +323 | except: +324 | print() +325 | / else: +326 | | pass + | |________^ RUF050 +327 | finally: +328 | print() + | + = help: Remove branch + +ℹ Safe fix +322 322 | print() +323 323 | except: +324 324 | print() +325 |-else: +326 |- pass +327 325 | finally: +328 326 | print() +329 327 | + +RUF050_try.py:347:1: RUF050 [*] Empty code branch + | +345 | else: +346 | print() +347 | / finally: +348 | | ... + | |_______^ RUF050 + | + = help: Remove branch + +ℹ Safe fix +344 344 | print() +345 345 | else: +346 346 | print() +347 |-finally: +348 |- ... +349 347 | +350 348 | +351 349 | # No error diff --git a/crates/ruff_python_ast/src/identifier.rs b/crates/ruff_python_ast/src/identifier.rs index 566b9c79251be..8a711864ba3aa 100644 --- a/crates/ruff_python_ast/src/identifier.rs +++ b/crates/ruff_python_ast/src/identifier.rs @@ -111,7 +111,7 @@ pub fn except(handler: &ExceptHandler, source: &str) -> TextRange { } /// Return the [`TextRange`] of the `else` token in a `For` or `While` statement. -pub fn else_(stmt: &Stmt, source: &str) -> Option { +pub fn else_loop(stmt: &Stmt, source: &str) -> Option { let (Stmt::For(ast::StmtFor { body, orelse, .. }) | Stmt::While(ast::StmtWhile { body, orelse, .. })) = stmt else { @@ -129,6 +129,50 @@ pub fn else_(stmt: &Stmt, source: &str) -> Option { .next() } +/// Return the [`TextRange`] of the `else` token in a `Try` statement. +pub fn else_try(stmt: &Stmt, source: &str) -> Option { + let Stmt::Try(stmt_try) = stmt else { + return None; + }; + + let handlers = &stmt_try.handlers; + let mut body = &stmt_try.body[..]; + + if let [.., ExceptHandler::ExceptHandler(last_handler)] = &handlers[..] { + body = &last_handler.body[..]; + }; + + IdentifierTokenizer::starts_at( + body.last().expect("Expected body to be non-empty").end(), + source, + ) + .next() +} + +/// Return the [`TextRange`] of the `finally` token in a `Try` statement. +pub fn finally(stmt: &Stmt, source: &str) -> Option { + let Stmt::Try(stmt_try) = stmt else { + return None; + }; + + let (handlers, orelse) = (&stmt_try.handlers, &stmt_try.orelse); + let mut body = &stmt_try.body[..]; + + if let [.., ExceptHandler::ExceptHandler(last_handler)] = &handlers[..] { + body = &last_handler.body[..]; + }; + + if !orelse.is_empty() { + body = &orelse[..]; + }; + + IdentifierTokenizer::starts_at( + body.last().expect("Expected body to be non-empty").end(), + source, + ) + .next() +} + /// Return `true` if the given character starts a valid Python identifier. /// /// Python identifiers must start with an alphabetic character or an underscore. diff --git a/crates/ruff_python_ast_integration_tests/tests/identifier.rs b/crates/ruff_python_ast_integration_tests/tests/identifier.rs index 324390b8454c9..530e7d16bc1fa 100644 --- a/crates/ruff_python_ast_integration_tests/tests/identifier.rs +++ b/crates/ruff_python_ast_integration_tests/tests/identifier.rs @@ -1,23 +1,60 @@ use ruff_python_ast::identifier; use ruff_python_parser::{parse_module, ParseError}; -use ruff_text_size::{TextRange, TextSize}; +use ruff_text_size::TextRange; + +macro_rules! test { + ($func:expr, $contents:expr, $expected:expr, $start:expr, $end:expr) => {{ + let contents = $contents.trim(); + + let stmts = parse_module(contents)?.into_suite(); + let stmt = stmts.first().unwrap(); + let range = $func(stmt, contents).unwrap(); + + assert_eq!(&contents[range], $expected); + assert_eq!(range, TextRange::new($start.into(), $end.into())); + + Ok(()) + }}; +} #[test] -fn extract_else_range() -> Result<(), ParseError> { +fn extract_else_range_loop() -> Result<(), ParseError> { let contents = r" for x in y: pass else: pass -" - .trim(); - let stmts = parse_module(contents)?.into_suite(); - let stmt = stmts.first().unwrap(); - let range = identifier::else_(stmt, contents).unwrap(); - assert_eq!(&contents[range], "else"); - assert_eq!( - range, - TextRange::new(TextSize::from(21), TextSize::from(25)) - ); - Ok(()) +"; + + test!(identifier::else_loop, contents, "else", 21, 25) +} + +#[test] +fn extract_else_range_try() -> Result<(), ParseError> { + let contents = r" +try: + pass +except: + pass +else: + pass +"; + + test!(identifier::else_try, contents, "else", 31, 35) +} + +#[test] +fn extract_finally_range_try() -> Result<(), ParseError> { + let contents = r" +try: + pass +except: + pass +else: + pass +finally: + pass +"; + + test!(identifier::finally, contents, "finally", 46, 53) } diff --git a/ruff.schema.json b/ruff.schema.json index 72ff32f77bace..5f3ec0b96dec0 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -3845,6 +3845,7 @@ "RUF041", "RUF048", "RUF05", + "RUF050", "RUF052", "RUF055", "RUF1", @@ -4159,4 +4160,4 @@ ] } } -} \ No newline at end of file +} From 8ef79379558d2fe4c316849e4be3a7463cccaa8f Mon Sep 17 00:00:00 2001 From: InSyncWithFoo Date: Wed, 4 Dec 2024 02:45:49 +0000 Subject: [PATCH 2/2] Clippy --- crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs | 4 ++-- ruff.schema.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs b/crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs index f1e0562a14eca..ab1a4b3ae39af 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/empty_branch.rs @@ -52,7 +52,7 @@ fn empty_branch(checker: &mut Checker, range: TextRange, fix: Option) { let mut diagnostic = Diagnostic::new(EmptyBranch { fixable }, range); diagnostic.try_set_optional_fix(|| Ok(fix)); - checker.diagnostics.push(diagnostic) + checker.diagnostics.push(diagnostic); } /// RUF050: `if`-`elif`-`else` @@ -235,7 +235,7 @@ pub(crate) fn empty_branch_try(checker: &mut Checker, stmt: &Stmt) { /// Whether `body` contains only `pass` or `...` statement. fn body_is_empty(body: &[Stmt]) -> bool { - if body.len() == 0 { + if body.is_empty() { return false; } diff --git a/ruff.schema.json b/ruff.schema.json index 5f3ec0b96dec0..fd8e6cbbf090c 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -4160,4 +4160,4 @@ ] } } -} +} \ No newline at end of file