aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists')
-rw-r--r--crates/ide_assists/src/handlers/apply_demorgan.rs87
1 files changed, 76 insertions, 11 deletions
diff --git a/crates/ide_assists/src/handlers/apply_demorgan.rs b/crates/ide_assists/src/handlers/apply_demorgan.rs
index a1c339603..5c936a510 100644
--- a/crates/ide_assists/src/handlers/apply_demorgan.rs
+++ b/crates/ide_assists/src/handlers/apply_demorgan.rs
@@ -1,3 +1,5 @@
1use std::collections::VecDeque;
2
1use syntax::ast::{self, AstNode}; 3use syntax::ast::{self, AstNode};
2 4
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; 5use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
@@ -30,19 +32,52 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
30 return None; 32 return None;
31 } 33 }
32 34
33 let lhs = expr.lhs()?; 35 let mut expr = expr;
34 let lhs_range = lhs.syntax().text_range(); 36
35 let not_lhs = invert_boolean_expression(&ctx.sema, lhs); 37 // Walk up the tree while we have the same binary operator
38 while let Some(parent_expr) = expr.syntax().parent().and_then(ast::BinExpr::cast) {
39 if let Some(parent_op) = expr.op_kind() {
40 if parent_op == op {
41 expr = parent_expr
42 }
43 }
44 }
45
46 let mut expr_stack = vec![expr.clone()];
47 let mut terms = Vec::new();
48 let mut op_ranges = Vec::new();
49
50 // Find all the children with the same binary operator
51 while let Some(expr) = expr_stack.pop() {
52 let mut traverse_bin_expr_arm = |expr| {
53 if let ast::Expr::BinExpr(bin_expr) = expr {
54 if let Some(expr_op) = bin_expr.op_kind() {
55 if expr_op == op {
56 expr_stack.push(bin_expr);
57 } else {
58 terms.push(ast::Expr::BinExpr(bin_expr));
59 }
60 } else {
61 terms.push(ast::Expr::BinExpr(bin_expr));
62 }
63 } else {
64 terms.push(expr);
65 }
66 };
36 67
37 let rhs = expr.rhs()?; 68 op_ranges.extend(expr.op_token().map(|t| t.text_range()));
38 let rhs_range = rhs.syntax().text_range(); 69 traverse_bin_expr_arm(expr.lhs()?);
39 let not_rhs = invert_boolean_expression(&ctx.sema, rhs); 70 traverse_bin_expr_arm(expr.rhs()?);
71 }
40 72
41 acc.add( 73 acc.add(
42 AssistId("apply_demorgan", AssistKind::RefactorRewrite), 74 AssistId("apply_demorgan", AssistKind::RefactorRewrite),
43 "Apply De Morgan's law", 75 "Apply De Morgan's law",
44 op_range, 76 op_range,
45 |edit| { 77 |edit| {
78 terms.sort_by_key(|t| t.syntax().text_range().start());
79 let mut terms = VecDeque::from(terms);
80
46 let paren_expr = expr.syntax().parent().and_then(|parent| ast::ParenExpr::cast(parent)); 81 let paren_expr = expr.syntax().parent().and_then(|parent| ast::ParenExpr::cast(parent));
47 82
48 let neg_expr = paren_expr 83 let neg_expr = paren_expr
@@ -57,11 +92,18 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
57 } 92 }
58 }); 93 });
59 94
60 edit.replace(op_range, opposite_op); 95 for op_range in op_ranges {
96 edit.replace(op_range, opposite_op);
97 }
61 98
62 if let Some(paren_expr) = paren_expr { 99 if let Some(paren_expr) = paren_expr {
63 edit.replace(lhs_range, not_lhs.syntax().text()); 100 for term in terms {
64 edit.replace(rhs_range, not_rhs.syntax().text()); 101 let range = term.syntax().text_range();
102 let not_term = invert_boolean_expression(&ctx.sema, term);
103
104 edit.replace(range, not_term.syntax().text());
105 }
106
65 if let Some(neg_expr) = neg_expr { 107 if let Some(neg_expr) = neg_expr {
66 cov_mark::hit!(demorgan_double_negation); 108 cov_mark::hit!(demorgan_double_negation);
67 edit.replace(neg_expr.op_token().unwrap().text_range(), ""); 109 edit.replace(neg_expr.op_token().unwrap().text_range(), "");
@@ -70,8 +112,25 @@ pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<(
70 edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!("); 112 edit.replace(paren_expr.l_paren_token().unwrap().text_range(), "!(");
71 } 113 }
72 } else { 114 } else {
73 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text())); 115 if let Some(lhs) = terms.pop_front() {
74 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text())); 116 let lhs_range = lhs.syntax().text_range();
117 let not_lhs = invert_boolean_expression(&ctx.sema, lhs);
118
119 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
120 }
121
122 if let Some(rhs) = terms.pop_back() {
123 let rhs_range = rhs.syntax().text_range();
124 let not_rhs = invert_boolean_expression(&ctx.sema, rhs);
125
126 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
127 }
128
129 for term in terms {
130 let term_range = term.syntax().text_range();
131 let not_term = invert_boolean_expression(&ctx.sema, term);
132 edit.replace(term_range, not_term.syntax().text());
133 }
75 } 134 }
76 }, 135 },
77 ) 136 )
@@ -180,6 +239,12 @@ fn f() {
180 } 239 }
181 240
182 #[test] 241 #[test]
242 fn demorgan_multiple_terms() {
243 check_assist(apply_demorgan, "fn f() { x ||$0 y || z }", "fn f() { !(!x && !y && !z) }");
244 check_assist(apply_demorgan, "fn f() { x || y ||$0 z }", "fn f() { !(!x && !y && !z) }");
245 }
246
247 #[test]
183 fn demorgan_doesnt_apply_with_cursor_not_on_op() { 248 fn demorgan_doesnt_apply_with_cursor_not_on_op() {
184 check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }") 249 check_assist_not_applicable(apply_demorgan, "fn f() { $0 !x || !x }")
185 } 250 }