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