aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/apply_demorgan.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers/apply_demorgan.rs')
-rw-r--r--crates/assists/src/handlers/apply_demorgan.rs93
1 files changed, 93 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/apply_demorgan.rs b/crates/assists/src/handlers/apply_demorgan.rs
new file mode 100644
index 000000000..1a6fdafda
--- /dev/null
+++ b/crates/assists/src/handlers/apply_demorgan.rs
@@ -0,0 +1,93 @@
1use syntax::ast::{self, AstNode};
2
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: apply_demorgan
6//
7// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
8// This transforms expressions of the form `!l || !r` into `!(l && r)`.
9// This also works with `&&`. This assist can only be applied with the cursor
10// on either `||` or `&&`, with both operands being a negation of some kind.
11// This means something of the form `!x` or `x != y`.
12//
13// ```
14// fn main() {
15// if x != 4 ||<|> !y {}
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// if !(x == 4 && y) {}
22// }
23// ```
24pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
26 let op = expr.op_kind()?;
27 let op_range = expr.op_token()?.text_range();
28 let opposite_op = opposite_logic_op(op)?;
29 let cursor_in_range = op_range.contains_range(ctx.frange.range);
30 if !cursor_in_range {
31 return None;
32 }
33
34 let lhs = expr.lhs()?;
35 let lhs_range = lhs.syntax().text_range();
36 let not_lhs = invert_boolean_expression(lhs);
37
38 let rhs = expr.rhs()?;
39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs);
41
42 acc.add(
43 AssistId("apply_demorgan", AssistKind::RefactorRewrite),
44 "Apply De Morgan's law",
45 op_range,
46 |edit| {
47 edit.replace(op_range, opposite_op);
48 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
49 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
50 },
51 )
52}
53
54// Return the opposite text for a given logical operator, if it makes sense
55fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
56 match kind {
57 ast::BinOp::BooleanOr => Some("&&"),
58 ast::BinOp::BooleanAnd => Some("||"),
59 _ => None,
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 use crate::tests::{check_assist, check_assist_not_applicable};
68
69 #[test]
70 fn demorgan_turns_and_into_or() {
71 check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x || x) }")
72 }
73
74 #[test]
75 fn demorgan_turns_or_into_and() {
76 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x && x) }")
77 }
78
79 #[test]
80 fn demorgan_removes_inequality() {
81 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x && x) }")
82 }
83
84 #[test]
85 fn demorgan_general_case() {
86 check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x && !x) }")
87 }
88
89 #[test]
90 fn demorgan_doesnt_apply_with_cursor_not_on_op() {
91 check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
92 }
93}