aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/introduce_variable.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/introduce_variable.rs')
-rw-r--r--crates/ra_assists/src/introduce_variable.rs432
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 @@
1use hir::db::HirDatabase;
2use 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
9use crate::{AssistCtx, Assist};
10
11pub(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
58fn 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.
63fn 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.
78fn 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)]
105mod 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 "
114fn foo() {
115 foo(<|>1 + 1<|>);
116}",
117 "
118fn 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 "
130fn foo() {
131 <|>1 + 1<|>;
132}",
133 "
134fn 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 "
145fn foo() {
146 <|>1<|> + 1;
147}",
148 "
149fn 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 "
161fn foo() {
162 bar(<|>1 + 1<|>)
163}",
164 "
165fn 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 "
177fn foo() {
178 <|>bar(1 + 1)<|>
179}",
180 "
181fn 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 "
193fn foo() {
194 <|>{ let x = 0; x }<|>
195 something_else();
196}",
197 "
198fn 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 "
210fn main() {
211 let x = true;
212 let tuple = match x {
213 true => (<|>2 + 2<|>, true)
214 _ => (0, false)
215 };
216}
217",
218 "
219fn 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 "
235fn 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 "
247fn 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 "
267fn main() {
268 let lambda = |x: u32| <|>x * 2<|>;
269}
270",
271 "
272fn 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 "
284fn main() {
285 let lambda = |x: u32| { <|>x * 2<|> };
286}
287",
288 "
289fn 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 "
301fn main() {
302 let o = S<|>ome(true);
303}
304",
305 "
306fn 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 "
319fn main() {
320 let v = b<|>ar.foo();
321}
322",
323 "
324fn 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 "
337fn foo() -> u32 {
338 r<|>eturn 2 + 2;
339}
340",
341 "
342fn 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 "
355fn main() {
356 let result = loop {
357 b<|>reak 2 + 2;
358 };
359}
360",
361 "
362fn 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 "
377fn main() {
378 let v = 0f32 a<|>s u32;
379}
380",
381 "
382fn 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 "
395fn 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 "
407fn 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 "
421fn 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}