ide_assists/handlers/
invert_if.rs

1use ide_db::syntax_helpers::node_ext::is_pattern_cond;
2use syntax::{
3    T,
4    ast::{self, AstNode},
5};
6
7use crate::{
8    AssistId,
9    assist_context::{AssistContext, Assists},
10    utils::invert_boolean_expression_legacy,
11};
12
13// Assist: invert_if
14//
15// This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
16// This also works with `!=`. This assist can only be applied with the cursor on `if`.
17//
18// ```
19// fn main() {
20//     if$0 !y { A } else { B }
21// }
22// ```
23// ->
24// ```
25// fn main() {
26//     if y { B } else { A }
27// }
28// ```
29pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
30    let if_keyword = ctx
31        .find_token_syntax_at_offset(T![if])
32        .or_else(|| ctx.find_token_syntax_at_offset(T![else]))?;
33    let expr = ast::IfExpr::cast(if_keyword.parent()?)?;
34    let if_range = if_keyword.text_range();
35    let cursor_in_range = if_range.contains_range(ctx.selection_trimmed());
36    if !cursor_in_range {
37        return None;
38    }
39
40    let cond = expr.condition()?;
41    // This assist should not apply for if-let.
42    if is_pattern_cond(cond.clone()) {
43        return None;
44    }
45
46    let then_node = expr.then_branch()?.syntax().clone();
47    let else_block = match expr.else_branch()? {
48        ast::ElseBranch::Block(it) => it,
49        ast::ElseBranch::IfExpr(_) => return None,
50    };
51
52    acc.add(AssistId::refactor_rewrite("invert_if"), "Invert if", if_range, |edit| {
53        let flip_cond = invert_boolean_expression_legacy(cond.clone());
54        edit.replace_ast(cond, flip_cond);
55
56        let else_node = else_block.syntax();
57        let else_range = else_node.text_range();
58        let then_range = then_node.text_range();
59
60        edit.replace(else_range, then_node.text());
61        edit.replace(then_range, else_node.text());
62    })
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    use crate::tests::{check_assist, check_assist_not_applicable};
70
71    #[test]
72    fn invert_if_composite_condition() {
73        check_assist(
74            invert_if,
75            "fn f() { i$0f x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
76            "fn f() { if !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
77        )
78    }
79
80    #[test]
81    fn invert_if_remove_not_parentheses() {
82        check_assist(
83            invert_if,
84            "fn f() { i$0f !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
85            "fn f() { if x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
86        )
87    }
88
89    #[test]
90    fn invert_if_remove_inequality() {
91        check_assist(
92            invert_if,
93            "fn f() { i$0f x != 3 { 1 } else { 3 + 2 } }",
94            "fn f() { if x == 3 { 3 + 2 } else { 1 } }",
95        )
96    }
97
98    #[test]
99    fn invert_if_remove_not() {
100        check_assist(
101            invert_if,
102            "fn f() { $0if !cond { 3 * 2 } else { 1 } }",
103            "fn f() { if cond { 1 } else { 3 * 2 } }",
104        )
105    }
106
107    #[test]
108    fn invert_if_general_case() {
109        check_assist(
110            invert_if,
111            "fn f() { i$0f cond { 3 * 2 } else { 1 } }",
112            "fn f() { if !cond { 1 } else { 3 * 2 } }",
113        )
114    }
115
116    #[test]
117    fn invert_if_on_else_keyword() {
118        check_assist(
119            invert_if,
120            "fn f() { if cond { 3 * 2 } e$0lse { 1 } }",
121            "fn f() { if !cond { 1 } else { 3 * 2 } }",
122        )
123    }
124
125    #[test]
126    fn invert_if_doesnt_apply_with_cursor_not_on_if() {
127        check_assist_not_applicable(invert_if, "fn f() { if !$0cond { 3 * 2 } else { 1 } }")
128    }
129
130    #[test]
131    fn invert_if_doesnt_apply_with_if_let() {
132        check_assist_not_applicable(
133            invert_if,
134            "fn f() { i$0f let Some(_) = Some(1) { 1 } else { 0 } }",
135        )
136    }
137
138    #[test]
139    fn invert_if_doesnt_apply_with_if_let_chain() {
140        check_assist_not_applicable(
141            invert_if,
142            "fn f() { i$0f x && let Some(_) = Some(1) { 1 } else { 0 } }",
143        );
144        check_assist_not_applicable(
145            invert_if,
146            "fn f() { i$0f let Some(_) = Some(1) && x { 1 } else { 0 } }",
147        );
148    }
149
150    #[test]
151    fn invert_if_option_case() {
152        check_assist(
153            invert_if,
154            "fn f() { if$0 doc_style.is_some() { Class::DocComment } else { Class::Comment } }",
155            "fn f() { if doc_style.is_none() { Class::Comment } else { Class::DocComment } }",
156        )
157    }
158
159    #[test]
160    fn invert_if_result_case() {
161        check_assist(
162            invert_if,
163            "fn f() { i$0f doc_style.is_err() { Class::Err } else { Class::Ok } }",
164            "fn f() { if doc_style.is_ok() { Class::Ok } else { Class::Err } }",
165        )
166    }
167}