diff options
Diffstat (limited to 'crates/ra_assists/src/introduce_variable.rs')
-rw-r--r-- | crates/ra_assists/src/introduce_variable.rs | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/crates/ra_assists/src/introduce_variable.rs b/crates/ra_assists/src/introduce_variable.rs new file mode 100644 index 000000000..c937a816c --- /dev/null +++ b/crates/ra_assists/src/introduce_variable.rs | |||
@@ -0,0 +1,432 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode}, | ||
4 | SyntaxKind::{ | ||
5 | WHITESPACE, MATCH_ARM, LAMBDA_EXPR, PATH_EXPR, BREAK_EXPR, LOOP_EXPR, RETURN_EXPR, COMMENT | ||
6 | }, SyntaxNode, TextUnit, | ||
7 | }; | ||
8 | |||
9 | use crate::{AssistCtx, Assist}; | ||
10 | |||
11 | pub(crate) fn introduce_variable<'a>(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
12 | let node = ctx.covering_node(); | ||
13 | if !valid_covering_node(node) { | ||
14 | return None; | ||
15 | } | ||
16 | let expr = node.ancestors().filter_map(valid_target_expr).next()?; | ||
17 | let (anchor_stmt, wrap_in_block) = anchor_stmt(expr)?; | ||
18 | let indent = anchor_stmt.prev_sibling()?; | ||
19 | if indent.kind() != WHITESPACE { | ||
20 | return None; | ||
21 | } | ||
22 | ctx.build("introduce variable", move |edit| { | ||
23 | let mut buf = String::new(); | ||
24 | |||
25 | let cursor_offset = if wrap_in_block { | ||
26 | buf.push_str("{ let var_name = "); | ||
27 | TextUnit::of_str("{ let ") | ||
28 | } else { | ||
29 | buf.push_str("let var_name = "); | ||
30 | TextUnit::of_str("let ") | ||
31 | }; | ||
32 | |||
33 | expr.syntax().text().push_to(&mut buf); | ||
34 | let full_stmt = ast::ExprStmt::cast(anchor_stmt); | ||
35 | let is_full_stmt = if let Some(expr_stmt) = full_stmt { | ||
36 | Some(expr.syntax()) == expr_stmt.expr().map(|e| e.syntax()) | ||
37 | } else { | ||
38 | false | ||
39 | }; | ||
40 | if is_full_stmt { | ||
41 | if !full_stmt.unwrap().has_semi() { | ||
42 | buf.push_str(";"); | ||
43 | } | ||
44 | edit.replace(expr.syntax().range(), buf); | ||
45 | } else { | ||
46 | buf.push_str(";"); | ||
47 | indent.text().push_to(&mut buf); | ||
48 | edit.replace(expr.syntax().range(), "var_name".to_string()); | ||
49 | edit.insert(anchor_stmt.range().start(), buf); | ||
50 | if wrap_in_block { | ||
51 | edit.insert(anchor_stmt.range().end(), " }"); | ||
52 | } | ||
53 | } | ||
54 | edit.set_cursor(anchor_stmt.range().start() + cursor_offset); | ||
55 | }) | ||
56 | } | ||
57 | |||
58 | fn valid_covering_node(node: &SyntaxNode) -> bool { | ||
59 | node.kind() != COMMENT | ||
60 | } | ||
61 | /// Check wether the node is a valid expression which can be extracted to a variable. | ||
62 | /// In general that's true for any expression, but in some cases that would produce invalid code. | ||
63 | fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> { | ||
64 | return match node.kind() { | ||
65 | PATH_EXPR => None, | ||
66 | BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), | ||
67 | RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), | ||
68 | LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), | ||
69 | _ => ast::Expr::cast(node), | ||
70 | }; | ||
71 | } | ||
72 | |||
73 | /// Returns the syntax node which will follow the freshly introduced var | ||
74 | /// and a boolean indicating whether we have to wrap it within a { } block | ||
75 | /// to produce correct code. | ||
76 | /// It can be a statement, the last in a block expression or a wanna be block | ||
77 | /// expression like a lamba or match arm. | ||
78 | fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> { | ||
79 | expr.syntax().ancestors().find_map(|node| { | ||
80 | if ast::Stmt::cast(node).is_some() { | ||
81 | return Some((node, false)); | ||
82 | } | ||
83 | |||
84 | if let Some(expr) = node | ||
85 | .parent() | ||
86 | .and_then(ast::Block::cast) | ||
87 | .and_then(|it| it.expr()) | ||
88 | { | ||
89 | if expr.syntax() == node { | ||
90 | return Some((node, false)); | ||
91 | } | ||
92 | } | ||
93 | |||
94 | if let Some(parent) = node.parent() { | ||
95 | if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR { | ||
96 | return Some((node, true)); | ||
97 | } | ||
98 | } | ||
99 | |||
100 | None | ||
101 | }) | ||
102 | } | ||
103 | |||
104 | #[cfg(test)] | ||
105 | mod tests { | ||
106 | use super::*; | ||
107 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range}; | ||
108 | |||
109 | #[test] | ||
110 | fn test_introduce_var_simple() { | ||
111 | check_assist_range( | ||
112 | introduce_variable, | ||
113 | " | ||
114 | fn foo() { | ||
115 | foo(<|>1 + 1<|>); | ||
116 | }", | ||
117 | " | ||
118 | fn foo() { | ||
119 | let <|>var_name = 1 + 1; | ||
120 | foo(var_name); | ||
121 | }", | ||
122 | ); | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn test_introduce_var_expr_stmt() { | ||
127 | check_assist_range( | ||
128 | introduce_variable, | ||
129 | " | ||
130 | fn foo() { | ||
131 | <|>1 + 1<|>; | ||
132 | }", | ||
133 | " | ||
134 | fn foo() { | ||
135 | let <|>var_name = 1 + 1; | ||
136 | }", | ||
137 | ); | ||
138 | } | ||
139 | |||
140 | #[test] | ||
141 | fn test_introduce_var_part_of_expr_stmt() { | ||
142 | check_assist_range( | ||
143 | introduce_variable, | ||
144 | " | ||
145 | fn foo() { | ||
146 | <|>1<|> + 1; | ||
147 | }", | ||
148 | " | ||
149 | fn foo() { | ||
150 | let <|>var_name = 1; | ||
151 | var_name + 1; | ||
152 | }", | ||
153 | ); | ||
154 | } | ||
155 | |||
156 | #[test] | ||
157 | fn test_introduce_var_last_expr() { | ||
158 | check_assist_range( | ||
159 | introduce_variable, | ||
160 | " | ||
161 | fn foo() { | ||
162 | bar(<|>1 + 1<|>) | ||
163 | }", | ||
164 | " | ||
165 | fn foo() { | ||
166 | let <|>var_name = 1 + 1; | ||
167 | bar(var_name) | ||
168 | }", | ||
169 | ); | ||
170 | } | ||
171 | |||
172 | #[test] | ||
173 | fn test_introduce_var_last_full_expr() { | ||
174 | check_assist_range( | ||
175 | introduce_variable, | ||
176 | " | ||
177 | fn foo() { | ||
178 | <|>bar(1 + 1)<|> | ||
179 | }", | ||
180 | " | ||
181 | fn foo() { | ||
182 | let <|>var_name = bar(1 + 1); | ||
183 | var_name | ||
184 | }", | ||
185 | ); | ||
186 | } | ||
187 | |||
188 | #[test] | ||
189 | fn test_introduce_var_block_expr_second_to_last() { | ||
190 | check_assist_range( | ||
191 | introduce_variable, | ||
192 | " | ||
193 | fn foo() { | ||
194 | <|>{ let x = 0; x }<|> | ||
195 | something_else(); | ||
196 | }", | ||
197 | " | ||
198 | fn foo() { | ||
199 | let <|>var_name = { let x = 0; x }; | ||
200 | something_else(); | ||
201 | }", | ||
202 | ); | ||
203 | } | ||
204 | |||
205 | #[test] | ||
206 | fn test_introduce_var_in_match_arm_no_block() { | ||
207 | check_assist_range( | ||
208 | introduce_variable, | ||
209 | " | ||
210 | fn main() { | ||
211 | let x = true; | ||
212 | let tuple = match x { | ||
213 | true => (<|>2 + 2<|>, true) | ||
214 | _ => (0, false) | ||
215 | }; | ||
216 | } | ||
217 | ", | ||
218 | " | ||
219 | fn main() { | ||
220 | let x = true; | ||
221 | let tuple = match x { | ||
222 | true => { let <|>var_name = 2 + 2; (var_name, true) } | ||
223 | _ => (0, false) | ||
224 | }; | ||
225 | } | ||
226 | ", | ||
227 | ); | ||
228 | } | ||
229 | |||
230 | #[test] | ||
231 | fn test_introduce_var_in_match_arm_with_block() { | ||
232 | check_assist_range( | ||
233 | introduce_variable, | ||
234 | " | ||
235 | fn main() { | ||
236 | let x = true; | ||
237 | let tuple = match x { | ||
238 | true => { | ||
239 | let y = 1; | ||
240 | (<|>2 + y<|>, true) | ||
241 | } | ||
242 | _ => (0, false) | ||
243 | }; | ||
244 | } | ||
245 | ", | ||
246 | " | ||
247 | fn main() { | ||
248 | let x = true; | ||
249 | let tuple = match x { | ||
250 | true => { | ||
251 | let y = 1; | ||
252 | let <|>var_name = 2 + y; | ||
253 | (var_name, true) | ||
254 | } | ||
255 | _ => (0, false) | ||
256 | }; | ||
257 | } | ||
258 | ", | ||
259 | ); | ||
260 | } | ||
261 | |||
262 | #[test] | ||
263 | fn test_introduce_var_in_closure_no_block() { | ||
264 | check_assist_range( | ||
265 | introduce_variable, | ||
266 | " | ||
267 | fn main() { | ||
268 | let lambda = |x: u32| <|>x * 2<|>; | ||
269 | } | ||
270 | ", | ||
271 | " | ||
272 | fn main() { | ||
273 | let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; | ||
274 | } | ||
275 | ", | ||
276 | ); | ||
277 | } | ||
278 | |||
279 | #[test] | ||
280 | fn test_introduce_var_in_closure_with_block() { | ||
281 | check_assist_range( | ||
282 | introduce_variable, | ||
283 | " | ||
284 | fn main() { | ||
285 | let lambda = |x: u32| { <|>x * 2<|> }; | ||
286 | } | ||
287 | ", | ||
288 | " | ||
289 | fn main() { | ||
290 | let lambda = |x: u32| { let <|>var_name = x * 2; var_name }; | ||
291 | } | ||
292 | ", | ||
293 | ); | ||
294 | } | ||
295 | |||
296 | #[test] | ||
297 | fn test_introduce_var_path_simple() { | ||
298 | check_assist( | ||
299 | introduce_variable, | ||
300 | " | ||
301 | fn main() { | ||
302 | let o = S<|>ome(true); | ||
303 | } | ||
304 | ", | ||
305 | " | ||
306 | fn main() { | ||
307 | let <|>var_name = Some(true); | ||
308 | let o = var_name; | ||
309 | } | ||
310 | ", | ||
311 | ); | ||
312 | } | ||
313 | |||
314 | #[test] | ||
315 | fn test_introduce_var_path_method() { | ||
316 | check_assist( | ||
317 | introduce_variable, | ||
318 | " | ||
319 | fn main() { | ||
320 | let v = b<|>ar.foo(); | ||
321 | } | ||
322 | ", | ||
323 | " | ||
324 | fn main() { | ||
325 | let <|>var_name = bar.foo(); | ||
326 | let v = var_name; | ||
327 | } | ||
328 | ", | ||
329 | ); | ||
330 | } | ||
331 | |||
332 | #[test] | ||
333 | fn test_introduce_var_return() { | ||
334 | check_assist( | ||
335 | introduce_variable, | ||
336 | " | ||
337 | fn foo() -> u32 { | ||
338 | r<|>eturn 2 + 2; | ||
339 | } | ||
340 | ", | ||
341 | " | ||
342 | fn foo() -> u32 { | ||
343 | let <|>var_name = 2 + 2; | ||
344 | return var_name; | ||
345 | } | ||
346 | ", | ||
347 | ); | ||
348 | } | ||
349 | |||
350 | #[test] | ||
351 | fn test_introduce_var_break() { | ||
352 | check_assist( | ||
353 | introduce_variable, | ||
354 | " | ||
355 | fn main() { | ||
356 | let result = loop { | ||
357 | b<|>reak 2 + 2; | ||
358 | }; | ||
359 | } | ||
360 | ", | ||
361 | " | ||
362 | fn main() { | ||
363 | let result = loop { | ||
364 | let <|>var_name = 2 + 2; | ||
365 | break var_name; | ||
366 | }; | ||
367 | } | ||
368 | ", | ||
369 | ); | ||
370 | } | ||
371 | |||
372 | #[test] | ||
373 | fn test_introduce_var_for_cast() { | ||
374 | check_assist( | ||
375 | introduce_variable, | ||
376 | " | ||
377 | fn main() { | ||
378 | let v = 0f32 a<|>s u32; | ||
379 | } | ||
380 | ", | ||
381 | " | ||
382 | fn main() { | ||
383 | let <|>var_name = 0f32 as u32; | ||
384 | let v = var_name; | ||
385 | } | ||
386 | ", | ||
387 | ); | ||
388 | } | ||
389 | |||
390 | #[test] | ||
391 | fn test_introduce_var_for_return_not_applicable() { | ||
392 | check_assist_not_applicable( | ||
393 | introduce_variable, | ||
394 | " | ||
395 | fn foo() { | ||
396 | r<|>eturn; | ||
397 | } | ||
398 | ", | ||
399 | ); | ||
400 | } | ||
401 | |||
402 | #[test] | ||
403 | fn test_introduce_var_for_break_not_applicable() { | ||
404 | check_assist_not_applicable( | ||
405 | introduce_variable, | ||
406 | " | ||
407 | fn main() { | ||
408 | loop { | ||
409 | b<|>reak; | ||
410 | }; | ||
411 | } | ||
412 | ", | ||
413 | ); | ||
414 | } | ||
415 | |||
416 | #[test] | ||
417 | fn test_introduce_var_in_comment_not_applicable() { | ||
418 | check_assist_not_applicable( | ||
419 | introduce_variable, | ||
420 | " | ||
421 | fn main() { | ||
422 | let x = true; | ||
423 | let tuple = match x { | ||
424 | // c<|>omment | ||
425 | true => (2 + 2, true) | ||
426 | _ => (0, false) | ||
427 | }; | ||
428 | } | ||
429 | ", | ||
430 | ); | ||
431 | } | ||
432 | } | ||