ide_assists/handlers/
merge_nested_if.rs1use syntax::{
2 T,
3 ast::{self, AstNode, BinaryOp, edit::AstNodeEdit},
4};
5
6use crate::{
7 AssistId,
8 assist_context::{AssistContext, Assists},
9};
10pub(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 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 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 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
83fn 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}