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