aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/move_guard.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/move_guard.rs')
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs308
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 @@
1use ra_syntax::{
2 ast,
3 ast::{AstNode, AstToken, IfExpr, MatchArm},
4 TextUnit,
5};
6
7use 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// ```
34pub(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// ```
91pub(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)]
132mod 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}