diff options
Diffstat (limited to 'crates/ra_assists/src/handlers/move_guard.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/move_guard.rs | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs new file mode 100644 index 000000000..2b91ce7c4 --- /dev/null +++ b/crates/ra_assists/src/handlers/move_guard.rs | |||
@@ -0,0 +1,308 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast, | ||
3 | ast::{AstNode, AstToken, IfExpr, MatchArm}, | ||
4 | TextUnit, | ||
5 | }; | ||
6 | |||
7 | use crate::{Assist, AssistCtx, AssistId}; | ||
8 | |||
9 | // Assist: move_guard_to_arm_body | ||
10 | // | ||
11 | // Moves match guard into match arm body. | ||
12 | // | ||
13 | // ``` | ||
14 | // enum Action { Move { distance: u32 }, Stop } | ||
15 | // | ||
16 | // fn handle(action: Action) { | ||
17 | // match action { | ||
18 | // Action::Move { distance } <|>if distance > 10 => foo(), | ||
19 | // _ => (), | ||
20 | // } | ||
21 | // } | ||
22 | // ``` | ||
23 | // -> | ||
24 | // ``` | ||
25 | // enum Action { Move { distance: u32 }, Stop } | ||
26 | // | ||
27 | // fn handle(action: Action) { | ||
28 | // match action { | ||
29 | // Action::Move { distance } => if distance > 10 { foo() }, | ||
30 | // _ => (), | ||
31 | // } | ||
32 | // } | ||
33 | // ``` | ||
34 | pub(crate) fn move_guard_to_arm_body(ctx: AssistCtx) -> Option<Assist> { | ||
35 | let match_arm = ctx.find_node_at_offset::<MatchArm>()?; | ||
36 | let guard = match_arm.guard()?; | ||
37 | let space_before_guard = guard.syntax().prev_sibling_or_token(); | ||
38 | |||
39 | let guard_conditions = guard.expr()?; | ||
40 | let arm_expr = match_arm.expr()?; | ||
41 | let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text()); | ||
42 | |||
43 | ctx.add_assist(AssistId("move_guard_to_arm_body"), "Move guard to arm body", |edit| { | ||
44 | edit.target(guard.syntax().text_range()); | ||
45 | let offseting_amount = match space_before_guard.and_then(|it| it.into_token()) { | ||
46 | Some(tok) => { | ||
47 | if let Some(_) = ast::Whitespace::cast(tok.clone()) { | ||
48 | let ele = tok.text_range(); | ||
49 | edit.delete(ele); | ||
50 | ele.len() | ||
51 | } else { | ||
52 | TextUnit::from(0) | ||
53 | } | ||
54 | } | ||
55 | _ => TextUnit::from(0), | ||
56 | }; | ||
57 | |||
58 | edit.delete(guard.syntax().text_range()); | ||
59 | edit.replace_node_and_indent(arm_expr.syntax(), buf); | ||
60 | edit.set_cursor( | ||
61 | arm_expr.syntax().text_range().start() + TextUnit::from(3) - offseting_amount, | ||
62 | ); | ||
63 | }) | ||
64 | } | ||
65 | |||
66 | // Assist: move_arm_cond_to_match_guard | ||
67 | // | ||
68 | // Moves if expression from match arm body into a guard. | ||
69 | // | ||
70 | // ``` | ||
71 | // enum Action { Move { distance: u32 }, Stop } | ||
72 | // | ||
73 | // fn handle(action: Action) { | ||
74 | // match action { | ||
75 | // Action::Move { distance } => <|>if distance > 10 { foo() }, | ||
76 | // _ => (), | ||
77 | // } | ||
78 | // } | ||
79 | // ``` | ||
80 | // -> | ||
81 | // ``` | ||
82 | // enum Action { Move { distance: u32 }, Stop } | ||
83 | // | ||
84 | // fn handle(action: Action) { | ||
85 | // match action { | ||
86 | // Action::Move { distance } if distance > 10 => foo(), | ||
87 | // _ => (), | ||
88 | // } | ||
89 | // } | ||
90 | // ``` | ||
91 | pub(crate) fn move_arm_cond_to_match_guard(ctx: AssistCtx) -> Option<Assist> { | ||
92 | let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?; | ||
93 | let last_match_pat = match_arm.pats().last()?; | ||
94 | |||
95 | let arm_body = match_arm.expr()?; | ||
96 | let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?; | ||
97 | let cond = if_expr.condition()?; | ||
98 | let then_block = if_expr.then_branch()?; | ||
99 | |||
100 | // Not support if with else branch | ||
101 | if let Some(_) = if_expr.else_branch() { | ||
102 | return None; | ||
103 | } | ||
104 | // Not support moving if let to arm guard | ||
105 | if let Some(_) = cond.pat() { | ||
106 | return None; | ||
107 | } | ||
108 | |||
109 | let buf = format!(" if {}", cond.syntax().text()); | ||
110 | |||
111 | ctx.add_assist( | ||
112 | AssistId("move_arm_cond_to_match_guard"), | ||
113 | "Move condition to match guard", | ||
114 | |edit| { | ||
115 | edit.target(if_expr.syntax().text_range()); | ||
116 | let then_only_expr = then_block.block().and_then(|it| it.statements().next()).is_none(); | ||
117 | |||
118 | match &then_block.block().and_then(|it| it.expr()) { | ||
119 | Some(then_expr) if then_only_expr => { | ||
120 | edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text()) | ||
121 | } | ||
122 | _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()), | ||
123 | } | ||
124 | |||
125 | edit.insert(last_match_pat.syntax().text_range().end(), buf); | ||
126 | edit.set_cursor(last_match_pat.syntax().text_range().end() + TextUnit::from(1)); | ||
127 | }, | ||
128 | ) | ||
129 | } | ||
130 | |||
131 | #[cfg(test)] | ||
132 | mod tests { | ||
133 | use super::*; | ||
134 | |||
135 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
136 | |||
137 | #[test] | ||
138 | fn move_guard_to_arm_body_target() { | ||
139 | check_assist_target( | ||
140 | move_guard_to_arm_body, | ||
141 | r#" | ||
142 | fn f() { | ||
143 | let t = 'a'; | ||
144 | let chars = "abcd"; | ||
145 | match t { | ||
146 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
147 | _ => true | ||
148 | } | ||
149 | } | ||
150 | "#, | ||
151 | r#"if chars.clone().next() == Some('\n')"#, | ||
152 | ); | ||
153 | } | ||
154 | |||
155 | #[test] | ||
156 | fn move_guard_to_arm_body_works() { | ||
157 | check_assist( | ||
158 | move_guard_to_arm_body, | ||
159 | r#" | ||
160 | fn f() { | ||
161 | let t = 'a'; | ||
162 | let chars = "abcd"; | ||
163 | match t { | ||
164 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
165 | _ => true | ||
166 | } | ||
167 | } | ||
168 | "#, | ||
169 | r#" | ||
170 | fn f() { | ||
171 | let t = 'a'; | ||
172 | let chars = "abcd"; | ||
173 | match t { | ||
174 | '\r' => if chars.clone().next() == Some('\n') { <|>false }, | ||
175 | _ => true | ||
176 | } | ||
177 | } | ||
178 | "#, | ||
179 | ); | ||
180 | } | ||
181 | |||
182 | #[test] | ||
183 | fn move_guard_to_arm_body_works_complex_match() { | ||
184 | check_assist( | ||
185 | move_guard_to_arm_body, | ||
186 | r#" | ||
187 | fn f() { | ||
188 | match x { | ||
189 | <|>y @ 4 | y @ 5 if y > 5 => true, | ||
190 | _ => false | ||
191 | } | ||
192 | } | ||
193 | "#, | ||
194 | r#" | ||
195 | fn f() { | ||
196 | match x { | ||
197 | y @ 4 | y @ 5 => if y > 5 { <|>true }, | ||
198 | _ => false | ||
199 | } | ||
200 | } | ||
201 | "#, | ||
202 | ); | ||
203 | } | ||
204 | |||
205 | #[test] | ||
206 | fn move_arm_cond_to_match_guard_works() { | ||
207 | check_assist( | ||
208 | move_arm_cond_to_match_guard, | ||
209 | r#" | ||
210 | fn f() { | ||
211 | let t = 'a'; | ||
212 | let chars = "abcd"; | ||
213 | match t { | ||
214 | '\r' => if chars.clone().next() == Some('\n') { <|>false }, | ||
215 | _ => true | ||
216 | } | ||
217 | } | ||
218 | "#, | ||
219 | r#" | ||
220 | fn f() { | ||
221 | let t = 'a'; | ||
222 | let chars = "abcd"; | ||
223 | match t { | ||
224 | '\r' <|>if chars.clone().next() == Some('\n') => false, | ||
225 | _ => true | ||
226 | } | ||
227 | } | ||
228 | "#, | ||
229 | ); | ||
230 | } | ||
231 | |||
232 | #[test] | ||
233 | fn move_arm_cond_to_match_guard_if_let_not_works() { | ||
234 | check_assist_not_applicable( | ||
235 | move_arm_cond_to_match_guard, | ||
236 | r#" | ||
237 | fn f() { | ||
238 | let t = 'a'; | ||
239 | let chars = "abcd"; | ||
240 | match t { | ||
241 | '\r' => if let Some(_) = chars.clone().next() { <|>false }, | ||
242 | _ => true | ||
243 | } | ||
244 | } | ||
245 | "#, | ||
246 | ); | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn move_arm_cond_to_match_guard_if_empty_body_works() { | ||
251 | check_assist( | ||
252 | move_arm_cond_to_match_guard, | ||
253 | r#" | ||
254 | fn f() { | ||
255 | let t = 'a'; | ||
256 | let chars = "abcd"; | ||
257 | match t { | ||
258 | '\r' => if chars.clone().next().is_some() { <|> }, | ||
259 | _ => true | ||
260 | } | ||
261 | } | ||
262 | "#, | ||
263 | r#" | ||
264 | fn f() { | ||
265 | let t = 'a'; | ||
266 | let chars = "abcd"; | ||
267 | match t { | ||
268 | '\r' <|>if chars.clone().next().is_some() => { }, | ||
269 | _ => true | ||
270 | } | ||
271 | } | ||
272 | "#, | ||
273 | ); | ||
274 | } | ||
275 | |||
276 | #[test] | ||
277 | fn move_arm_cond_to_match_guard_if_multiline_body_works() { | ||
278 | check_assist( | ||
279 | move_arm_cond_to_match_guard, | ||
280 | r#" | ||
281 | fn f() { | ||
282 | let mut t = 'a'; | ||
283 | let chars = "abcd"; | ||
284 | match t { | ||
285 | '\r' => if chars.clone().next().is_some() { | ||
286 | t = 'e';<|> | ||
287 | false | ||
288 | }, | ||
289 | _ => true | ||
290 | } | ||
291 | } | ||
292 | "#, | ||
293 | r#" | ||
294 | fn f() { | ||
295 | let mut t = 'a'; | ||
296 | let chars = "abcd"; | ||
297 | match t { | ||
298 | '\r' <|>if chars.clone().next().is_some() => { | ||
299 | t = 'e'; | ||
300 | false | ||
301 | }, | ||
302 | _ => true | ||
303 | } | ||
304 | } | ||
305 | "#, | ||
306 | ); | ||
307 | } | ||
308 | } | ||