aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
authorLúcás Meier <[email protected]>2019-10-03 21:48:35 +0100
committerLúcás Meier <[email protected]>2019-10-03 21:48:35 +0100
commite769a54502648011ede7d7434e0a16a8ab740c56 (patch)
treef7a1c297c34a21eec9308de960df9063a6f5c878 /crates/ra_assists
parentad65ba40624a0f9d0ecbfbfc2671e20cb98db029 (diff)
Create an assist for applying De Morgan's law
Fixes #1807 This assist can transform expressions of the form `!x || !y` into `!(x && y)`. This also works with `&&`. This assist will only trigger if the cursor is on the central logical operator. The main limitation of this current implementation is that both operands need to be an explicit negation, either of the form `!x`, or `x != y`. More operands could be accepted, but this would complicate the implementation quite a bit.
Diffstat (limited to 'crates/ra_assists')
-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}