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