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