aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api_light/src/assists.rs8
-rw-r--r--crates/ra_ide_api_light/src/assists/introduce_variable.rs297
-rw-r--r--crates/ra_ide_api_light/src/test_utils.rs12
3 files changed, 302 insertions, 15 deletions
diff --git a/crates/ra_ide_api_light/src/assists.rs b/crates/ra_ide_api_light/src/assists.rs
index aea8397c9..8905b0419 100644
--- a/crates/ra_ide_api_light/src/assists.rs
+++ b/crates/ra_ide_api_light/src/assists.rs
@@ -197,6 +197,14 @@ fn check_assist(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &s
197} 197}
198 198
199#[cfg(test)] 199#[cfg(test)]
200fn check_assist_not_applicable(assist: fn(AssistCtx) -> Option<Assist>, text: &str) {
201 crate::test_utils::check_action_not_applicable(text, |file, off| {
202 let range = TextRange::offset_len(off, 0.into());
203 AssistCtx::new(file, range).apply(assist)
204 })
205}
206
207#[cfg(test)]
200fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) { 208fn check_assist_range(assist: fn(AssistCtx) -> Option<Assist>, before: &str, after: &str) {
201 crate::test_utils::check_action_range(before, after, |file, range| { 209 crate::test_utils::check_action_range(before, after, |file, range| {
202 AssistCtx::new(file, range).apply(assist) 210 AssistCtx::new(file, range).apply(assist)
diff --git a/crates/ra_ide_api_light/src/assists/introduce_variable.rs b/crates/ra_ide_api_light/src/assists/introduce_variable.rs
index 3e4434c23..ed13bddc4 100644
--- a/crates/ra_ide_api_light/src/assists/introduce_variable.rs
+++ b/crates/ra_ide_api_light/src/assists/introduce_variable.rs
@@ -1,16 +1,19 @@
1use ra_syntax::{ 1use ra_syntax::{
2 ast::{self, AstNode}, 2 ast::{self, AstNode},
3 SyntaxKind::WHITESPACE, 3 SyntaxKind::{
4 SyntaxNode, TextUnit, 4 WHITESPACE, MATCH_ARM, LAMBDA_EXPR, PATH_EXPR, BREAK_EXPR, LOOP_EXPR, RETURN_EXPR, COMMENT
5 }, SyntaxNode, TextUnit,
5}; 6};
6 7
7use crate::assists::{AssistCtx, Assist}; 8use crate::assists::{AssistCtx, Assist};
8 9
9pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> { 10pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> {
10 let node = ctx.covering_node(); 11 let node = ctx.covering_node();
11 let expr = node.ancestors().filter_map(ast::Expr::cast).next()?; 12 if !valid_covering_node(node) {
12 13 return None;
13 let anchor_stmt = anchor_stmt(expr)?; 14 }
15 let expr = node.ancestors().filter_map(valid_target_expr).next()?;
16 let (anchor_stmt, wrap_in_block) = anchor_stmt(expr)?;
14 let indent = anchor_stmt.prev_sibling()?; 17 let indent = anchor_stmt.prev_sibling()?;
15 if indent.kind() != WHITESPACE { 18 if indent.kind() != WHITESPACE {
16 return None; 19 return None;
@@ -18,7 +21,14 @@ pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> {
18 ctx.build("introduce variable", move |edit| { 21 ctx.build("introduce variable", move |edit| {
19 let mut buf = String::new(); 22 let mut buf = String::new();
20 23
21 buf.push_str("let var_name = "); 24 let cursor_offset = if wrap_in_block {
25 buf.push_str("{ let var_name = ");
26 TextUnit::of_str("{ let ")
27 } else {
28 buf.push_str("let var_name = ");
29 TextUnit::of_str("let ")
30 };
31
22 expr.syntax().text().push_to(&mut buf); 32 expr.syntax().text().push_to(&mut buf);
23 let full_stmt = ast::ExprStmt::cast(anchor_stmt); 33 let full_stmt = ast::ExprStmt::cast(anchor_stmt);
24 let is_full_stmt = if let Some(expr_stmt) = full_stmt { 34 let is_full_stmt = if let Some(expr_stmt) = full_stmt {
@@ -36,35 +46,64 @@ pub fn introduce_variable<'a>(ctx: AssistCtx) -> Option<Assist> {
36 indent.text().push_to(&mut buf); 46 indent.text().push_to(&mut buf);
37 edit.replace(expr.syntax().range(), "var_name".to_string()); 47 edit.replace(expr.syntax().range(), "var_name".to_string());
38 edit.insert(anchor_stmt.range().start(), buf); 48 edit.insert(anchor_stmt.range().start(), buf);
49 if wrap_in_block {
50 edit.insert(anchor_stmt.range().end(), " }");
51 }
39 } 52 }
40 edit.set_cursor(anchor_stmt.range().start() + TextUnit::of_str("let ")); 53 edit.set_cursor(anchor_stmt.range().start() + cursor_offset);
41 }) 54 })
42} 55}
43 56
44/// Statement or last in the block expression, which will follow 57fn valid_covering_node(node: &SyntaxNode) -> bool {
45/// the freshly introduced var. 58 node.kind() != COMMENT
46fn anchor_stmt(expr: &ast::Expr) -> Option<&SyntaxNode> { 59}
47 expr.syntax().ancestors().find(|&node| { 60/// Check wether the node is a valid expression which can be extracted to a variable.
61/// In general that's true for any expression, but in some cases that would produce invalid code.
62fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> {
63 return match node.kind() {
64 PATH_EXPR => None,
65 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
66 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
67 LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
68 _ => ast::Expr::cast(node),
69 };
70}
71
72/// Returns the syntax node which will follow the freshly introduced var
73/// and a boolean indicating whether we have to wrap it within a { } block
74/// to produce correct code.
75/// It can be a statement, the last in a block expression or a wanna be block
76/// expression like a lamba or match arm.
77fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
78 expr.syntax().ancestors().find_map(|node| {
48 if ast::Stmt::cast(node).is_some() { 79 if ast::Stmt::cast(node).is_some() {
49 return true; 80 return Some((node, false));
50 } 81 }
82
51 if let Some(expr) = node 83 if let Some(expr) = node
52 .parent() 84 .parent()
53 .and_then(ast::Block::cast) 85 .and_then(ast::Block::cast)
54 .and_then(|it| it.expr()) 86 .and_then(|it| it.expr())
55 { 87 {
56 if expr.syntax() == node { 88 if expr.syntax() == node {
57 return true; 89 return Some((node, false));
90 }
91 }
92
93 if let Some(parent) = node.parent() {
94 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
95 return Some((node, true));
58 } 96 }
59 } 97 }
60 false 98
99 None
61 }) 100 })
62} 101}
63 102
64#[cfg(test)] 103#[cfg(test)]
65mod tests { 104mod tests {
66 use super::*; 105 use super::*;
67 use crate::assists::check_assist_range; 106 use crate::assists::{ check_assist, check_assist_not_applicable, check_assist_range };
68 107
69 #[test] 108 #[test]
70 fn test_introduce_var_simple() { 109 fn test_introduce_var_simple() {
@@ -161,4 +200,232 @@ fn foo() {
161}", 200}",
162 ); 201 );
163 } 202 }
203
204 #[test]
205 fn test_introduce_var_in_match_arm_no_block() {
206 check_assist_range(
207 introduce_variable,
208 "
209fn main() {
210 let x = true;
211 let tuple = match x {
212 true => (<|>2 + 2<|>, true)
213 _ => (0, false)
214 };
215}
216",
217 "
218fn main() {
219 let x = true;
220 let tuple = match x {
221 true => { let <|>var_name = 2 + 2; (var_name, true) }
222 _ => (0, false)
223 };
224}
225",
226 );
227 }
228
229 #[test]
230 fn test_introduce_var_in_match_arm_with_block() {
231 check_assist_range(
232 introduce_variable,
233 "
234fn main() {
235 let x = true;
236 let tuple = match x {
237 true => {
238 let y = 1;
239 (<|>2 + y<|>, true)
240 }
241 _ => (0, false)
242 };
243}
244",
245 "
246fn main() {
247 let x = true;
248 let tuple = match x {
249 true => {
250 let y = 1;
251 let <|>var_name = 2 + y;
252 (var_name, true)
253 }
254 _ => (0, false)
255 };
256}
257",
258 );
259 }
260
261 #[test]
262 fn test_introduce_var_in_closure_no_block() {
263 check_assist_range(
264 introduce_variable,
265 "
266fn main() {
267 let lambda = |x: u32| <|>x * 2<|>;
268}
269",
270 "
271fn main() {
272 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
273}
274",
275 );
276 }
277
278 #[test]
279 fn test_introduce_var_in_closure_with_block() {
280 check_assist_range(
281 introduce_variable,
282 "
283fn main() {
284 let lambda = |x: u32| { <|>x * 2<|> };
285}
286",
287 "
288fn main() {
289 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
290}
291",
292 );
293 }
294
295 #[test]
296 fn test_introduce_var_path_simple() {
297 check_assist(
298 introduce_variable,
299 "
300fn main() {
301 let o = S<|>ome(true);
302}
303",
304 "
305fn main() {
306 let <|>var_name = Some(true);
307 let o = var_name;
308}
309",
310 );
311 }
312
313 #[test]
314 fn test_introduce_var_path_method() {
315 check_assist(
316 introduce_variable,
317 "
318fn main() {
319 let v = b<|>ar.foo();
320}
321",
322 "
323fn main() {
324 let <|>var_name = bar.foo();
325 let v = var_name;
326}
327",
328 );
329 }
330
331 #[test]
332 fn test_introduce_var_return() {
333 check_assist(
334 introduce_variable,
335 "
336fn foo() -> u32 {
337 r<|>eturn 2 + 2;
338}
339",
340 "
341fn foo() -> u32 {
342 let <|>var_name = 2 + 2;
343 return var_name;
344}
345",
346 );
347 }
348
349 #[test]
350 fn test_introduce_var_break() {
351 check_assist(
352 introduce_variable,
353 "
354fn main() {
355 let result = loop {
356 b<|>reak 2 + 2;
357 };
358}
359",
360 "
361fn main() {
362 let result = loop {
363 let <|>var_name = 2 + 2;
364 break var_name;
365 };
366}
367",
368 );
369 }
370
371 #[test]
372 fn test_introduce_var_for_cast() {
373 check_assist(
374 introduce_variable,
375 "
376fn main() {
377 let v = 0f32 a<|>s u32;
378}
379",
380 "
381fn main() {
382 let <|>var_name = 0f32 as u32;
383 let v = var_name;
384}
385",
386 );
387 }
388
389 #[test]
390 fn test_introduce_var_for_return_not_applicable() {
391 check_assist_not_applicable(
392 introduce_variable,
393 "
394fn foo() {
395 r<|>eturn;
396}
397",
398 );
399 }
400
401 #[test]
402 fn test_introduce_var_for_break_not_applicable() {
403 check_assist_not_applicable(
404 introduce_variable,
405 "
406fn main() {
407 loop {
408 b<|>reak;
409 };
410}
411",
412 );
413 }
414
415 #[test]
416 fn test_introduce_var_in_comment_not_applicable() {
417 check_assist_not_applicable(
418 introduce_variable,
419 "
420fn main() {
421 let x = true;
422 let tuple = match x {
423 // c<|>omment
424 true => (2 + 2, true)
425 _ => (0, false)
426 };
427}
428",
429 );
430 }
164} 431}
diff --git a/crates/ra_ide_api_light/src/test_utils.rs b/crates/ra_ide_api_light/src/test_utils.rs
index dc2470aa3..22ded2435 100644
--- a/crates/ra_ide_api_light/src/test_utils.rs
+++ b/crates/ra_ide_api_light/src/test_utils.rs
@@ -23,6 +23,18 @@ pub fn check_action<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>(
23 assert_eq_text!(after, &actual); 23 assert_eq_text!(after, &actual);
24} 24}
25 25
26pub fn check_action_not_applicable<F: Fn(&SourceFile, TextUnit) -> Option<LocalEdit>>(
27 text: &str,
28 f: F,
29) {
30 let (text_cursor_pos, text) = extract_offset(text);
31 let file = SourceFile::parse(&text);
32 assert!(
33 f(&file, text_cursor_pos).is_none(),
34 "code action is applicable but it shouldn't"
35 );
36}
37
26pub fn check_action_range<F: Fn(&SourceFile, TextRange) -> Option<LocalEdit>>( 38pub fn check_action_range<F: Fn(&SourceFile, TextRange) -> Option<LocalEdit>>(
27 before: &str, 39 before: &str,
28 after: &str, 40 after: &str,