Skip to main content

ide_assists/handlers/
add_braces.rs

1use either::Either;
2use syntax::{
3    AstNode, T,
4    ast::{self, edit::AstNodeEdit},
5    match_ast,
6};
7
8use crate::{AssistContext, AssistId, Assists};
9
10// Assist: add_braces
11//
12// Adds braces to closure bodies, match arm expressions and assignment bodies.
13//
14// ```
15// fn foo(n: i32) -> i32 {
16//     match n {
17//         1 =>$0 n + 1,
18//         _ => 0
19//     }
20// }
21// ```
22// ->
23// ```
24// fn foo(n: i32) -> i32 {
25//     match n {
26//         1 => {
27//             n + 1
28//         },
29//         _ => 0
30//     }
31// }
32// ```
33// ---
34// ```
35// fn foo(n: i32) -> i32 {
36//     let x =$0 n + 2;
37// }
38// ```
39// ->
40// ```
41// fn foo(n: i32) -> i32 {
42//     let x = {
43//         n + 2
44//     };
45// }
46// ```
47pub(crate) fn add_braces(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
48    let (expr_type, expr) = get_replacement_node(ctx)?;
49
50    acc.add(
51        AssistId::refactor_rewrite("add_braces"),
52        match expr_type {
53            ParentType::ClosureExpr => "Add braces to this closure body",
54            ParentType::MatchArmExpr => "Add braces to this match arm expression",
55            ParentType::Assignment => "Add braces to this assignment expression",
56        },
57        expr.syntax().text_range(),
58        |builder| {
59            let editor = builder.make_editor(expr.syntax());
60            let make = editor.make();
61
62            let new_expr = expr.reset_indent().indent(1.into());
63            let block_expr = make.block_expr(None, Some(new_expr));
64
65            editor.replace(expr.syntax(), block_expr.indent(expr.indent_level()).syntax());
66            builder.add_file_edits(ctx.vfs_file_id(), editor);
67        },
68    )
69}
70
71enum ParentType {
72    MatchArmExpr,
73    ClosureExpr,
74    Assignment,
75}
76
77fn get_replacement_node(ctx: &AssistContext<'_, '_>) -> Option<(ParentType, ast::Expr)> {
78    let node = ctx.find_node_at_offset::<Either<ast::MatchArm, ast::ClosureExpr>>();
79    let (parent_type, body) = if let Some(eq_token) = ctx.find_token_syntax_at_offset(T![=]) {
80        let parent = eq_token.parent()?;
81        let body = match_ast! {
82            match parent {
83                ast::LetStmt(it) => it.initializer()?,
84                ast::LetExpr(it) => it.expr()?,
85                ast::BinExpr(it) => it.rhs()?,
86                ast::Static(it) => it.body()?,
87                ast::Const(it) => it.body()?,
88                _ => return None,
89            }
90        };
91        (ParentType::Assignment, body)
92    } else if let Some(Either::Left(match_arm)) = &node {
93        let match_arm_expr = match_arm.expr()?;
94        (ParentType::MatchArmExpr, match_arm_expr)
95    } else if let Some(Either::Right(closure_expr)) = &node {
96        let body = closure_expr.body()?;
97        (ParentType::ClosureExpr, body)
98    } else {
99        return None;
100    };
101
102    if matches!(body, ast::Expr::BlockExpr(_)) {
103        return None;
104    }
105
106    Some((parent_type, body))
107}
108
109#[cfg(test)]
110mod tests {
111    use crate::tests::{check_assist, check_assist_not_applicable};
112
113    use super::*;
114
115    #[test]
116    fn suggest_add_braces_for_closure() {
117        check_assist(
118            add_braces,
119            r#"
120fn foo() {
121    t(|n|$0 n + 100);
122}
123"#,
124            r#"
125fn foo() {
126    t(|n| {
127        n + 100
128    });
129}
130"#,
131        );
132    }
133
134    #[test]
135    fn suggest_add_braces_for_closure_in_match() {
136        check_assist(
137            add_braces,
138            r#"
139fn foo() {
140    match () {
141        () => {
142            t(|n|$0 n + 100);
143        }
144    }
145}
146"#,
147            r#"
148fn foo() {
149    match () {
150        () => {
151            t(|n| {
152                n + 100
153            });
154        }
155    }
156}
157"#,
158        );
159    }
160
161    #[test]
162    fn suggest_add_braces_for_assignment() {
163        check_assist(
164            add_braces,
165            r#"
166fn foo() {
167    let x =$0 n + 100;
168}
169"#,
170            r#"
171fn foo() {
172    let x = {
173        n + 100
174    };
175}
176"#,
177        );
178
179        check_assist(
180            add_braces,
181            r#"
182fn foo() {
183    let x;
184    x =$0 n + 100;
185}
186"#,
187            r#"
188fn foo() {
189    let x;
190    x = {
191        n + 100
192    };
193}
194"#,
195        );
196
197        check_assist(
198            add_braces,
199            r#"
200fn foo() {
201    if let x =$0 n + 100 {}
202}
203"#,
204            r#"
205fn foo() {
206    if let x = {
207        n + 100
208    } {}
209}
210"#,
211        );
212    }
213
214    #[test]
215    fn suggest_add_braces_for_const_initializer() {
216        check_assist(
217            add_braces,
218            r#"
219const X: i32 =$0 1 + 2;
220"#,
221            r#"
222const X: i32 = {
223    1 + 2
224};
225"#,
226        );
227    }
228
229    #[test]
230    fn suggest_add_braces_for_static_initializer() {
231        check_assist(
232            add_braces,
233            r#"
234static X: i32 $0= 1 + 2;
235"#,
236            r#"
237static X: i32 = {
238    1 + 2
239};
240"#,
241        );
242    }
243
244    #[test]
245    fn no_assist_for_closures_with_braces() {
246        check_assist_not_applicable(
247            add_braces,
248            r#"
249fn foo() {
250    t(|n|$0 { n + 100 });
251}
252"#,
253        );
254    }
255
256    #[test]
257    fn suggest_add_braces_for_match() {
258        check_assist(
259            add_braces,
260            r#"
261fn foo() {
262    match n {
263        Some(n) $0=> 29,
264        _ => ()
265    };
266}
267"#,
268            r#"
269fn foo() {
270    match n {
271        Some(n) => {
272            29
273        },
274        _ => ()
275    };
276}
277"#,
278        );
279    }
280
281    #[test]
282    fn multiple_indent() {
283        check_assist(
284            add_braces,
285            r#"
286fn foo() {
287    {
288        match n {
289            Some(n) $0=> foo(
290                29,
291                30,
292            ),
293            _ => ()
294        };
295    }
296}
297"#,
298            r#"
299fn foo() {
300    {
301        match n {
302            Some(n) => {
303                foo(
304                    29,
305                    30,
306                )
307            },
308            _ => ()
309        };
310    }
311}
312"#,
313        );
314    }
315
316    #[test]
317    fn no_assist_for_match_with_braces() {
318        check_assist_not_applicable(
319            add_braces,
320            r#"
321fn foo() {
322    match n {
323        Some(n) $0=> { return 29; },
324        _ => ()
325    };
326}
327"#,
328        );
329    }
330}