diff options
author | Jake Goulding <[email protected]> | 2021-03-05 21:15:49 +0000 |
---|---|---|
committer | Jake Goulding <[email protected]> | 2021-03-12 15:19:54 +0000 |
commit | 63155d66f5268799df4c0261a64c31536b9f8a55 (patch) | |
tree | dc147d9296100ac0e6a5fc27f004075239dafb51 /crates/ide_assists/src/handlers | |
parent | c0459c53572f90fa9134192e432562af3daba5fa (diff) |
Allow applying De Morgan's law to multiple terms at once
Diffstat (limited to 'crates/ide_assists/src/handlers')
-rw-r--r-- | crates/ide_assists/src/handlers/apply_demorgan.rs | 87 |
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 @@ | |||
1 | use std::collections::VecDeque; | ||
2 | |||
1 | use syntax::ast::{self, AstNode}; | 3 | use syntax::ast::{self, AstNode}; |
2 | 4 | ||
3 | use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists}; | 5 | use 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 | } |