Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/apply_demorgan.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/apply_demorgan.rs | 115 |
1 files changed, 80 insertions, 35 deletions
diff --git a/crates/ide-assists/src/handlers/apply_demorgan.rs b/crates/ide-assists/src/handlers/apply_demorgan.rs index 753a9e56c3..3281adbcc3 100644 --- a/crates/ide-assists/src/handlers/apply_demorgan.rs +++ b/crates/ide-assists/src/handlers/apply_demorgan.rs @@ -6,7 +6,7 @@ use ide_db::{ syntax_helpers::node_ext::{for_each_tail_expr, walk_expr}, }; use syntax::{ - SyntaxKind, T, + NodeOrToken, SyntaxKind, T, ast::{ self, AstNode, Expr::BinExpr, @@ -38,15 +38,27 @@ use crate::{AssistContext, AssistId, Assists, utils::invert_boolean_expression}; // } // ``` pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let mut bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; + let mut bin_expr = if let Some(not) = ctx.find_token_syntax_at_offset(T![!]) + && let Some(NodeOrToken::Node(next)) = not.next_sibling_or_token() + && let Some(paren) = ast::ParenExpr::cast(next) + && let Some(ast::Expr::BinExpr(bin_expr)) = paren.expr() + { + bin_expr + } else { + let bin_expr = ctx.find_node_at_offset::<ast::BinExpr>()?; + let op_range = bin_expr.op_token()?.text_range(); + + // Is the cursor on the expression's logical operator? + if !op_range.contains_range(ctx.selection_trimmed()) { + return None; + } + + bin_expr + }; + let op = bin_expr.op_kind()?; let op_range = bin_expr.op_token()?.text_range(); - // Is the cursor on the expression's logical operator? - if !op_range.contains_range(ctx.selection_trimmed()) { - return None; - } - // Walk up the tree while we have the same binary operator while let Some(parent_expr) = bin_expr.syntax().parent().and_then(ast::BinExpr::cast) { match parent_expr.op_kind() { @@ -112,40 +124,37 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti op_range, |builder| { let make = SyntaxFactory::with_mappings(); - let paren_expr = bin_expr.syntax().parent().and_then(ast::ParenExpr::cast); - let neg_expr = paren_expr - .clone() + let (target_node, result_expr) = if let Some(neg_expr) = bin_expr + .syntax() + .parent() + .and_then(ast::ParenExpr::cast) .and_then(|paren_expr| paren_expr.syntax().parent()) .and_then(ast::PrefixExpr::cast) .filter(|prefix_expr| matches!(prefix_expr.op_kind(), Some(ast::UnaryOp::Not))) - .map(ast::Expr::PrefixExpr); - - let mut editor; - if let Some(paren_expr) = paren_expr { - if let Some(neg_expr) = neg_expr { - cov_mark::hit!(demorgan_double_negation); - let parent = neg_expr.syntax().parent(); - editor = builder.make_editor(neg_expr.syntax()); - - if parent.is_some_and(|parent| { - demorganed.needs_parens_in_place_of(&parent, neg_expr.syntax()) - }) { - cov_mark::hit!(demorgan_keep_parens_for_op_precedence2); - editor.replace(neg_expr.syntax(), make.expr_paren(demorganed).syntax()); - } else { - editor.replace(neg_expr.syntax(), demorganed.syntax()); - }; - } else { - cov_mark::hit!(demorgan_double_parens); - editor = builder.make_editor(paren_expr.syntax()); + { + cov_mark::hit!(demorgan_double_negation); + (ast::Expr::from(neg_expr).syntax().clone(), demorganed) + } else if let Some(paren_expr) = + bin_expr.syntax().parent().and_then(ast::ParenExpr::cast) + { + cov_mark::hit!(demorgan_double_parens); + (paren_expr.syntax().clone(), add_bang_paren(&make, demorganed)) + } else { + (bin_expr.syntax().clone(), add_bang_paren(&make, demorganed)) + }; - editor.replace(paren_expr.syntax(), add_bang_paren(&make, demorganed).syntax()); - } + let final_expr = if target_node + .parent() + .is_some_and(|p| result_expr.needs_parens_in_place_of(&p, &target_node)) + { + cov_mark::hit!(demorgan_keep_parens_for_op_precedence2); + make.expr_paren(result_expr).into() } else { - editor = builder.make_editor(bin_expr.syntax()); - editor.replace(bin_expr.syntax(), add_bang_paren(&make, demorganed).syntax()); - } + result_expr + }; + let mut editor = builder.make_editor(&target_node); + editor.replace(&target_node, final_expr.syntax()); editor.add_mappings(make.finish_with_mappings()); builder.add_file_edits(ctx.vfs_file_id(), editor); }, @@ -367,6 +376,15 @@ fn f() { !(S <= S || S < S) } } #[test] + fn demorgan_on_not() { + check_assist( + apply_demorgan, + "fn f() { $0!(1 || 3 && 4 || 5) }", + "fn f() { !1 && !(3 && 4) && !5 }", + ) + } + + #[test] fn demorgan_keep_pars_for_op_precedence() { check_assist( apply_demorgan, @@ -615,4 +633,31 @@ fn main() { "#, ); } + + #[test] + fn demorgan_method_call_receiver() { + check_assist( + apply_demorgan, + "fn f() { (x ||$0 !y).then_some(42) }", + "fn f() { (!(!x && y)).then_some(42) }", + ); + } + + #[test] + fn demorgan_method_call_receiver_complex() { + check_assist( + apply_demorgan, + "fn f() { (a && b ||$0 c && d).then_some(42) }", + "fn f() { (!(!(a && b) && !(c && d))).then_some(42) }", + ); + } + + #[test] + fn demorgan_method_call_receiver_chained() { + check_assist( + apply_demorgan, + "fn f() { (a ||$0 b).then_some(42).or(Some(0)) }", + "fn f() { (!(!a && !b)).then_some(42).or(Some(0)) }", + ); + } } |