diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/ra_assists/src/assists/apply_demorgan.rs | 115 |
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)`. | ||
4 | use hir::db::HirDatabase; | ||
5 | use ra_syntax::SyntaxNode; | ||
6 | use ra_syntax::ast::{AstNode, BinExpr, BinOp, Expr, PrefixOp}; | ||
7 | |||
8 | use crate::{Assist, AssistCtx, AssistId}; | ||
9 | |||
10 | // Return the opposite text for a given logical operator, if it makes sense | ||
11 | fn 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 | ||
20 | fn 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`. | ||
47 | pub(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)] | ||
74 | mod 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 | } | ||