]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/introduce_variable.rs
reformat the world
[rust.git] / crates / ra_assists / src / introduce_variable.rs
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(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     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.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
85             if expr.syntax() == node {
86                 return Some((node, false));
87             }
88         }
89
90         if let Some(parent) = node.parent() {
91             if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
92                 return Some((node, true));
93             }
94         }
95
96         None
97     })
98 }
99
100 #[cfg(test)]
101 mod tests {
102     use super::*;
103     use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range};
104
105     #[test]
106     fn test_introduce_var_simple() {
107         check_assist_range(
108             introduce_variable,
109             "
110 fn foo() {
111     foo(<|>1 + 1<|>);
112 }",
113             "
114 fn foo() {
115     let <|>var_name = 1 + 1;
116     foo(var_name);
117 }",
118         );
119     }
120
121     #[test]
122     fn test_introduce_var_expr_stmt() {
123         check_assist_range(
124             introduce_variable,
125             "
126 fn foo() {
127     <|>1 + 1<|>;
128 }",
129             "
130 fn foo() {
131     let <|>var_name = 1 + 1;
132 }",
133         );
134     }
135
136     #[test]
137     fn test_introduce_var_part_of_expr_stmt() {
138         check_assist_range(
139             introduce_variable,
140             "
141 fn foo() {
142     <|>1<|> + 1;
143 }",
144             "
145 fn foo() {
146     let <|>var_name = 1;
147     var_name + 1;
148 }",
149         );
150     }
151
152     #[test]
153     fn test_introduce_var_last_expr() {
154         check_assist_range(
155             introduce_variable,
156             "
157 fn foo() {
158     bar(<|>1 + 1<|>)
159 }",
160             "
161 fn foo() {
162     let <|>var_name = 1 + 1;
163     bar(var_name)
164 }",
165         );
166     }
167
168     #[test]
169     fn test_introduce_var_last_full_expr() {
170         check_assist_range(
171             introduce_variable,
172             "
173 fn foo() {
174     <|>bar(1 + 1)<|>
175 }",
176             "
177 fn foo() {
178     let <|>var_name = bar(1 + 1);
179     var_name
180 }",
181         );
182     }
183
184     #[test]
185     fn test_introduce_var_block_expr_second_to_last() {
186         check_assist_range(
187             introduce_variable,
188             "
189 fn foo() {
190     <|>{ let x = 0; x }<|>
191     something_else();
192 }",
193             "
194 fn foo() {
195     let <|>var_name = { let x = 0; x };
196     something_else();
197 }",
198         );
199     }
200
201     #[test]
202     fn test_introduce_var_in_match_arm_no_block() {
203         check_assist_range(
204             introduce_variable,
205             "
206 fn main() {
207     let x = true;
208     let tuple = match x {
209         true => (<|>2 + 2<|>, true)
210         _ => (0, false)
211     };
212 }
213 ",
214             "
215 fn main() {
216     let x = true;
217     let tuple = match x {
218         true => { let <|>var_name = 2 + 2; (var_name, true) }
219         _ => (0, false)
220     };
221 }
222 ",
223         );
224     }
225
226     #[test]
227     fn test_introduce_var_in_match_arm_with_block() {
228         check_assist_range(
229             introduce_variable,
230             "
231 fn main() {
232     let x = true;
233     let tuple = match x {
234         true => {
235             let y = 1;
236             (<|>2 + y<|>, true)
237         }
238         _ => (0, false)
239     };
240 }
241 ",
242             "
243 fn main() {
244     let x = true;
245     let tuple = match x {
246         true => {
247             let y = 1;
248             let <|>var_name = 2 + y;
249             (var_name, true)
250         }
251         _ => (0, false)
252     };
253 }
254 ",
255         );
256     }
257
258     #[test]
259     fn test_introduce_var_in_closure_no_block() {
260         check_assist_range(
261             introduce_variable,
262             "
263 fn main() {
264     let lambda = |x: u32| <|>x * 2<|>;
265 }
266 ",
267             "
268 fn main() {
269     let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
270 }
271 ",
272         );
273     }
274
275     #[test]
276     fn test_introduce_var_in_closure_with_block() {
277         check_assist_range(
278             introduce_variable,
279             "
280 fn main() {
281     let lambda = |x: u32| { <|>x * 2<|> };
282 }
283 ",
284             "
285 fn main() {
286     let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
287 }
288 ",
289         );
290     }
291
292     #[test]
293     fn test_introduce_var_path_simple() {
294         check_assist(
295             introduce_variable,
296             "
297 fn main() {
298     let o = S<|>ome(true);
299 }
300 ",
301             "
302 fn main() {
303     let <|>var_name = Some(true);
304     let o = var_name;
305 }
306 ",
307         );
308     }
309
310     #[test]
311     fn test_introduce_var_path_method() {
312         check_assist(
313             introduce_variable,
314             "
315 fn main() {
316     let v = b<|>ar.foo();
317 }
318 ",
319             "
320 fn main() {
321     let <|>var_name = bar.foo();
322     let v = var_name;
323 }
324 ",
325         );
326     }
327
328     #[test]
329     fn test_introduce_var_return() {
330         check_assist(
331             introduce_variable,
332             "
333 fn foo() -> u32 {
334     r<|>eturn 2 + 2;
335 }
336 ",
337             "
338 fn foo() -> u32 {
339     let <|>var_name = 2 + 2;
340     return var_name;
341 }
342 ",
343         );
344     }
345
346     #[test]
347     fn test_introduce_var_break() {
348         check_assist(
349             introduce_variable,
350             "
351 fn main() {
352     let result = loop {
353         b<|>reak 2 + 2;
354     };
355 }
356 ",
357             "
358 fn main() {
359     let result = loop {
360         let <|>var_name = 2 + 2;
361         break var_name;
362     };
363 }
364 ",
365         );
366     }
367
368     #[test]
369     fn test_introduce_var_for_cast() {
370         check_assist(
371             introduce_variable,
372             "
373 fn main() {
374     let v = 0f32 a<|>s u32;
375 }
376 ",
377             "
378 fn main() {
379     let <|>var_name = 0f32 as u32;
380     let v = var_name;
381 }
382 ",
383         );
384     }
385
386     #[test]
387     fn test_introduce_var_for_return_not_applicable() {
388         check_assist_not_applicable(
389             introduce_variable,
390             "
391 fn foo() {
392     r<|>eturn;
393 }
394 ",
395         );
396     }
397
398     #[test]
399     fn test_introduce_var_for_break_not_applicable() {
400         check_assist_not_applicable(
401             introduce_variable,
402             "
403 fn main() {
404     loop {
405         b<|>reak;
406     };
407 }
408 ",
409         );
410     }
411
412     #[test]
413     fn test_introduce_var_in_comment_not_applicable() {
414         check_assist_not_applicable(
415             introduce_variable,
416             "
417 fn main() {
418     let x = true;
419     let tuple = match x {
420         // c<|>omment
421         true => (2 + 2, true)
422         _ => (0, false)
423     };
424 }
425 ",
426         );
427     }
428 }