Skip to main content

ide_assists/handlers/
merge_nested_if.rs

1use syntax::{
2    T,
3    ast::{self, AstNode, BinaryOp, edit::AstNodeEdit},
4};
5
6use crate::{
7    AssistId,
8    assist_context::{AssistContext, Assists},
9};
10// Assist: merge_nested_if
11//
12// This transforms if expressions of the form `if x { if y {A} }` into `if x && y {A}`
13// This assist can only be applied with the cursor on `if`.
14//
15// ```
16// fn main() {
17//    i$0f x == 3 { if y == 4 { 1 } }
18// }
19// ```
20// ->
21// ```
22// fn main() {
23//    if x == 3 && y == 4 { 1 }
24// }
25// ```
26pub(crate) fn merge_nested_if(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
27    let if_keyword = ctx.find_token_syntax_at_offset(T![if])?;
28    let expr = ast::IfExpr::cast(if_keyword.parent()?)?;
29    let if_range = if_keyword.text_range();
30    let cursor_in_range = if_range.contains_range(ctx.selection_trimmed());
31    if !cursor_in_range {
32        return None;
33    }
34
35    //should not apply to if with else branch.
36    if expr.else_branch().is_some() {
37        return None;
38    }
39
40    let cond = expr.condition()?;
41
42    let cond_range = cond.syntax().text_range();
43
44    //check if the then branch is a nested if
45    let then_branch = expr.then_branch()?;
46    let stmt = then_branch.stmt_list()?;
47    if stmt.statements().count() != 0 {
48        return None;
49    }
50
51    let nested_if_to_merge = then_branch.tail_expr().and_then(|e| match e {
52        ast::Expr::IfExpr(e) => Some(e),
53        _ => None,
54    })?;
55    // should not apply to nested if with else branch.
56    if nested_if_to_merge.else_branch().is_some() {
57        return None;
58    }
59    let nested_if_cond = nested_if_to_merge.condition()?;
60
61    let nested_if_then_branch = nested_if_to_merge.then_branch()?;
62
63    acc.add(AssistId::refactor_rewrite("merge_nested_if"), "Merge nested if", if_range, |edit| {
64        let cond_text = if has_logic_op_or(&cond) {
65            format!("({})", cond.syntax().text())
66        } else {
67            cond.syntax().text().to_string()
68        };
69
70        let nested_if_cond_text = if has_logic_op_or(&nested_if_cond) {
71            format!("({})", nested_if_cond.syntax().text())
72        } else {
73            nested_if_cond.syntax().text().to_string()
74        };
75
76        let replace_cond = format!("{cond_text} && {nested_if_cond_text}");
77
78        edit.replace(cond_range, replace_cond);
79        edit.replace_ast(then_branch, nested_if_then_branch.dedent(1.into()));
80    })
81}
82
83/// Returns whether the given if condition has logical operators.
84fn has_logic_op_or(expr: &ast::Expr) -> bool {
85    match expr {
86        ast::Expr::BinExpr(bin_expr) => {
87            if let Some(kind) = bin_expr.op_kind() {
88                matches!(kind, BinaryOp::LogicOp(ast::LogicOp::Or))
89            } else {
90                false
91            }
92        }
93        _ => false,
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::tests::{check_assist, check_assist_not_applicable};
101
102    #[test]
103    fn merge_nested_if_test1() {
104        check_assist(
105            merge_nested_if,
106            "
107            fn f() {
108                i$0f x == 3 {
109                    if y == 4 {
110                        1
111                    }
112                }
113            }",
114            "
115            fn f() {
116                if x == 3 && y == 4 {
117                    1
118                }
119            }",
120        )
121    }
122
123    #[test]
124    fn merge_nested_if_test2() {
125        check_assist(
126            merge_nested_if,
127            "fn f() { i$0f x == 3 || y == 1 { if z == 4 { 1 } } }",
128            "fn f() { if (x == 3 || y == 1) && z == 4 { 1 } }",
129        )
130    }
131
132    #[test]
133    fn merge_nested_if_test3() {
134        check_assist(
135            merge_nested_if,
136            "fn f() { i$0f x == 3 && y == 1 { if z == 4 { 1 } } }",
137            "fn f() { if x == 3 && y == 1 && z == 4 { 1 } }",
138        )
139    }
140
141    #[test]
142    fn merge_nested_if_test4() {
143        check_assist(
144            merge_nested_if,
145            "fn f() { i$0f x == 3 && y == 1 { if z == 4 && q == 3 { 1 } } }",
146            "fn f() { if x == 3 && y == 1 && z == 4 && q == 3 { 1 } }",
147        )
148    }
149
150    #[test]
151    fn merge_nested_if_test5() {
152        check_assist(
153            merge_nested_if,
154            "fn f() { i$0f x == 3 && y == 1 { if z == 4 || q == 3 { 1 } } }",
155            "fn f() { if x == 3 && y == 1 && (z == 4 || q == 3) { 1 } }",
156        )
157    }
158
159    #[test]
160    fn merge_nested_if_test6() {
161        check_assist(
162            merge_nested_if,
163            "fn f() { i$0f x == 3 || y == 1 { if z == 4 || q == 3 { 1 } } }",
164            "fn f() { if (x == 3 || y == 1) && (z == 4 || q == 3) { 1 } }",
165        )
166    }
167
168    #[test]
169    fn merge_nested_if_test7() {
170        check_assist(
171            merge_nested_if,
172            "fn f() { i$0f x == 3 || y == 1 { if z == 4 && q == 3 { 1 } } }",
173            "fn f() { if (x == 3 || y == 1) && z == 4 && q == 3 { 1 } }",
174        )
175    }
176
177    #[test]
178    fn merge_nested_if_test8() {
179        check_assist(
180            merge_nested_if,
181            "fn f() { i$0f let Some(x) = y { if x == 4 { 1 } } }",
182            "fn f() { if let Some(x) = y && x == 4 { 1 } }",
183        )
184    }
185
186    #[test]
187    fn merge_nested_if_test9() {
188        check_assist(
189            merge_nested_if,
190            "fn f() { i$0f y == 0 { if let Some(x) = y { 1 } } }",
191            "fn f() { if y == 0 && let Some(x) = y { 1 } }",
192        )
193    }
194
195    #[test]
196    fn merge_nested_if_do_not_apply_to_if_with_else_branch() {
197        check_assist_not_applicable(
198            merge_nested_if,
199            "fn f() { i$0f x == 3 { if y == 4 { 1 } } else { 2 } }",
200        )
201    }
202
203    #[test]
204    fn merge_nested_if_do_not_apply_to_nested_if_with_else_branch() {
205        check_assist_not_applicable(
206            merge_nested_if,
207            "fn f() { i$0f x == 3 { if y == 4 { 1 } else { 2 } } }",
208        )
209    }
210
211    #[test]
212    fn merge_nested_if_do_not_apply_to_if_with_else_branch_and_nested_if() {
213        check_assist_not_applicable(
214            merge_nested_if,
215            "fn f() { i$0f x == 3 { if y == 4 { 1 } } else { if z == 5 { 2 } } }",
216        )
217    }
218
219    #[test]
220    fn merge_nested_if_do_not_apply_with_cursor_not_on_if() {
221        check_assist_not_applicable(merge_nested_if, "fn f() { if $0x==0 { if y == 3 { 1 } } }")
222    }
223
224    #[test]
225    fn merge_nested_if_do_not_apply_with_mulpiple_if() {
226        check_assist_not_applicable(
227            merge_nested_if,
228            "fn f() { i$0f x == 0 { if y == 3 { 1 } else if y == 4 { 2 } } }",
229        )
230    }
231    #[test]
232    fn merge_nested_if_do_not_apply_with_not_only_has_nested_if() {
233        check_assist_not_applicable(
234            merge_nested_if,
235            "fn f() { i$0f x == 0 { if y == 3 { foo(); } foo(); } }",
236        )
237    }
238
239    #[test]
240    fn merge_nested_if_do_not_apply_with_multiply_nested_if() {
241        check_assist_not_applicable(
242            merge_nested_if,
243            "fn f() { i$0f x == 0 { if y == 3 { foo(); } if z == 3 { 2 } } }",
244        )
245    }
246}