ide_assists/handlers/
add_braces.rs

1use either::Either;
2use syntax::{
3    AstNode, T,
4    ast::{self, edit::AstNodeEdit, syntax_factory::SyntaxFactory},
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 make = SyntaxFactory::with_mappings();
60            let mut editor = builder.make_editor(expr.syntax());
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
67            editor.add_mappings(make.finish_with_mappings());
68            builder.add_file_edits(ctx.vfs_file_id(), editor);
69        },
70    )
71}
72
73enum ParentType {
74    MatchArmExpr,
75    ClosureExpr,
76    Assignment,
77}
78
79fn get_replacement_node(ctx: &AssistContext<'_>) -> Option<(ParentType, ast::Expr)> {
80    let node = ctx.find_node_at_offset::<Either<ast::MatchArm, ast::ClosureExpr>>();
81    let (parent_type, body) = if let Some(eq_token) = ctx.find_token_syntax_at_offset(T![=]) {
82        let parent = eq_token.parent()?;
83        let body = match_ast! {
84            match parent {
85                ast::LetStmt(it) => it.initializer()?,
86                ast::LetExpr(it) => it.expr()?,
87                ast::Static(it) => it.body()?,
88                ast::Const(it) => it.body()?,
89                _ => return None,
90            }
91        };
92        (ParentType::Assignment, body)
93    } else if let Some(Either::Left(match_arm)) = &node {
94        let match_arm_expr = match_arm.expr()?;
95        (ParentType::MatchArmExpr, match_arm_expr)
96    } else if let Some(Either::Right(closure_expr)) = &node {
97        let body = closure_expr.body()?;
98        (ParentType::ClosureExpr, body)
99    } else {
100        return None;
101    };
102
103    if matches!(body, ast::Expr::BlockExpr(_)) {
104        return None;
105    }
106
107    Some((parent_type, body))
108}
109
110#[cfg(test)]
111mod tests {
112    use crate::tests::{check_assist, check_assist_not_applicable};
113
114    use super::*;
115
116    #[test]
117    fn suggest_add_braces_for_closure() {
118        check_assist(
119            add_braces,
120            r#"
121fn foo() {
122    t(|n|$0 n + 100);
123}
124"#,
125            r#"
126fn foo() {
127    t(|n| {
128        n + 100
129    });
130}
131"#,
132        );
133    }
134
135    #[test]
136    fn suggest_add_braces_for_closure_in_match() {
137        check_assist(
138            add_braces,
139            r#"
140fn foo() {
141    match () {
142        () => {
143            t(|n|$0 n + 100);
144        }
145    }
146}
147"#,
148            r#"
149fn foo() {
150    match () {
151        () => {
152            t(|n| {
153                n + 100
154            });
155        }
156    }
157}
158"#,
159        );
160    }
161
162    #[test]
163    fn suggest_add_braces_for_assignment() {
164        check_assist(
165            add_braces,
166            r#"
167fn foo() {
168    let x =$0 n + 100;
169}
170"#,
171            r#"
172fn foo() {
173    let x = {
174        n + 100
175    };
176}
177"#,
178        );
179    }
180
181    #[test]
182    fn no_assist_for_closures_with_braces() {
183        check_assist_not_applicable(
184            add_braces,
185            r#"
186fn foo() {
187    t(|n|$0 { n + 100 });
188}
189"#,
190        );
191    }
192
193    #[test]
194    fn suggest_add_braces_for_match() {
195        check_assist(
196            add_braces,
197            r#"
198fn foo() {
199    match n {
200        Some(n) $0=> 29,
201        _ => ()
202    };
203}
204"#,
205            r#"
206fn foo() {
207    match n {
208        Some(n) => {
209            29
210        },
211        _ => ()
212    };
213}
214"#,
215        );
216    }
217
218    #[test]
219    fn multiple_indent() {
220        check_assist(
221            add_braces,
222            r#"
223fn foo() {
224    {
225        match n {
226            Some(n) $0=> foo(
227                29,
228                30,
229            ),
230            _ => ()
231        };
232    }
233}
234"#,
235            r#"
236fn foo() {
237    {
238        match n {
239            Some(n) => {
240                foo(
241                    29,
242                    30,
243                )
244            },
245            _ => ()
246        };
247    }
248}
249"#,
250        );
251    }
252
253    #[test]
254    fn no_assist_for_match_with_braces() {
255        check_assist_not_applicable(
256            add_braces,
257            r#"
258fn foo() {
259    match n {
260        Some(n) $0=> { return 29; },
261        _ => ()
262    };
263}
264"#,
265        );
266    }
267}