aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src')
-rw-r--r--crates/ra_assists/src/assists/apply_demorgan.rs115
1 files changed, 115 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/apply_demorgan.rs b/crates/ra_assists/src/assists/apply_demorgan.rs
new file mode 100644
index 000000000..e4a8657ca
--- /dev/null
+++ b/crates/ra_assists/src/assists/apply_demorgan.rs
@@ -0,0 +1,115 @@
1//! This contains the functions associated with the demorgan assist.
2//! This assist transforms boolean expressions of the form `!a || !b` into
3//! `!(a && b)`.
4use hir::db::HirDatabase;
5use ra_syntax::SyntaxNode;
6use ra_syntax::ast::{AstNode, BinExpr, BinOp, Expr, PrefixOp};
7
8use crate::{Assist, AssistCtx, AssistId};
9
10// Return the opposite text for a given logical operator, if it makes sense
11fn opposite_logic_op(kind: BinOp) -> Option<&'static str> {
12 match kind {
13 BinOp::BooleanOr => Some("&&"),
14 BinOp::BooleanAnd => Some("||"),
15 _ => None,
16 }
17}
18
19// This function tries to undo unary negation, or inequality
20fn undo_negation(node: SyntaxNode) -> Option<String> {
21 match Expr::cast(node)? {
22 Expr::BinExpr(bin) => match bin.op_kind()? {
23 BinOp::NegatedEqualityTest => {
24 let lhs = bin.lhs()?.syntax().text();
25 let rhs = bin.rhs()?.syntax().text();
26 Some(format!("{} == {}", lhs, rhs))
27 }
28 _ => None
29 }
30 Expr::PrefixExpr(pe) => match pe.op_kind()? {
31 PrefixOp::Not => {
32 let child = pe.expr()?.syntax().text();
33 Some(String::from(child))
34 }
35 _ => None
36 }
37 _ => None
38 }
39}
40
41/// Assist for applying demorgan's law
42///
43/// This transforms expressions of the form `!l || !r` into `!(l && r)`.
44/// This also works with `&&`. This assist can only be applied with the cursor
45/// on either `||` or `&&`, with both operands being a negation of some kind.
46/// This means something of the form `!x` or `x != y`.
47pub(crate) fn apply_demorgan(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
48 let expr = ctx.node_at_offset::<BinExpr>()?;
49 let op = expr.op_kind()?;
50 let op_range = expr.op_token()?.text_range();
51 let opposite_op = opposite_logic_op(op)?;
52 let cursor_in_range = ctx.frange.range.is_subrange(&op_range);
53 if !cursor_in_range {
54 return None;
55 }
56 let lhs = expr.lhs()?.syntax().clone();
57 let lhs_range = lhs.text_range();
58 let rhs = expr.rhs()?.syntax().clone();
59 let rhs_range = rhs.text_range();
60 let not_lhs = undo_negation(lhs)?;
61 let not_rhs = undo_negation(rhs)?;
62
63
64 ctx.add_action(AssistId("apply_demorgan"), "apply demorgan's law", |edit| {
65 edit.target(op_range);
66 edit.replace(op_range, opposite_op);
67 edit.replace(lhs_range, format!("!({}", not_lhs));
68 edit.replace(rhs_range, format!("{})", not_rhs));
69 });
70 ctx.build()
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 use crate::helpers::{check_assist, check_assist_not_applicable};
78
79 #[test]
80 fn demorgan_turns_and_into_or() {
81 check_assist(
82 apply_demorgan,
83 "fn f() { !x &&<|> !x }",
84 "fn f() { !(x ||<|> x) }"
85 )
86 }
87
88 #[test]
89 fn demorgan_turns_or_into_and() {
90 check_assist(
91 apply_demorgan,
92 "fn f() { !x ||<|> !x }",
93 "fn f() { !(x &&<|> x) }"
94 )
95 }
96
97 #[test]
98 fn demorgan_removes_inequality() {
99 check_assist(
100 apply_demorgan,
101 "fn f() { x != x ||<|> !x }",
102 "fn f() { !(x == x &&<|> x) }"
103 )
104 }
105
106 #[test]
107 fn demorgan_doesnt_apply_with_cursor_not_on_op() {
108 check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
109 }
110
111 #[test]
112 fn demorgan_doesnt_apply_when_operands_arent_negated_already() {
113 check_assist_not_applicable(apply_demorgan, "fn f() { x ||<|> x }")
114 }
115}