aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/invert_if.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/invert_if.rs')
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs112
1 files changed, 112 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
new file mode 100644
index 000000000..983392f21
--- /dev/null
+++ b/crates/ra_assists/src/handlers/invert_if.rs
@@ -0,0 +1,112 @@
1use ra_syntax::ast::{self, make, AstNode};
2use ra_syntax::T;
3
4use crate::{Assist, AssistCtx, AssistId};
5
6// Assist: invert_if
7//
8// Apply invert_if
9// This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
10// This also works with `!=`. This assist can only be applied with the cursor
11// on `if`.
12//
13// ```
14// fn main() {
15// if<|> !y { A } else { B }
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// if y { B } else { A }
22// }
23// ```
24
25pub(crate) fn invert_if(ctx: AssistCtx) -> Option<Assist> {
26 let if_keyword = ctx.find_token_at_offset(T![if])?;
27 let expr = ast::IfExpr::cast(if_keyword.parent())?;
28 let if_range = if_keyword.text_range();
29 let cursor_in_range = ctx.frange.range.is_subrange(&if_range);
30 if !cursor_in_range {
31 return None;
32 }
33
34 let cond = expr.condition()?.expr()?;
35 let then_node = expr.then_branch()?.syntax().clone();
36
37 if let ast::ElseBranch::Block(else_block) = expr.else_branch()? {
38 let cond_range = cond.syntax().text_range();
39 let flip_cond = invert_boolean_expression(cond);
40 let else_node = else_block.syntax();
41 let else_range = else_node.text_range();
42 let then_range = then_node.text_range();
43 return ctx.add_assist(AssistId("invert_if"), "Invert if", |edit| {
44 edit.target(if_range);
45 edit.replace(cond_range, flip_cond.syntax().text());
46 edit.replace(else_range, then_node.text());
47 edit.replace(then_range, else_node.text());
48 });
49 }
50
51 None
52}
53
54pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
55 if let Some(expr) = invert_special_case(&expr) {
56 return expr;
57 }
58 make::expr_prefix(T![!], expr)
59}
60
61pub(crate) fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
62 match expr {
63 ast::Expr::BinExpr(bin) => match bin.op_kind()? {
64 ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
65 ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
66 _ => None,
67 },
68 ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => pe.expr(),
69 // FIXME:
70 // ast::Expr::Literal(true | false )
71 _ => None,
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 use crate::helpers::{check_assist, check_assist_not_applicable};
80
81 #[test]
82 fn invert_if_remove_inequality() {
83 check_assist(
84 invert_if,
85 "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }",
86 "fn f() { i<|>f x == 3 { 3 + 2 } else { 1 } }",
87 )
88 }
89
90 #[test]
91 fn invert_if_remove_not() {
92 check_assist(
93 invert_if,
94 "fn f() { <|>if !cond { 3 * 2 } else { 1 } }",
95 "fn f() { <|>if cond { 1 } else { 3 * 2 } }",
96 )
97 }
98
99 #[test]
100 fn invert_if_general_case() {
101 check_assist(
102 invert_if,
103 "fn f() { i<|>f cond { 3 * 2 } else { 1 } }",
104 "fn f() { i<|>f !cond { 1 } else { 3 * 2 } }",
105 )
106 }
107
108 #[test]
109 fn invert_if_doesnt_apply_with_cursor_not_on_if() {
110 check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }")
111 }
112}