1 use hir::db::HirDatabase;
5 WHITESPACE, MATCH_ARM, LAMBDA_EXPR, PATH_EXPR, BREAK_EXPR, LOOP_EXPR, RETURN_EXPR, COMMENT
6 }, SyntaxNode, TextUnit,
9 use crate::{AssistCtx, Assist};
11 pub(crate) fn introduce_variable(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12 let node = ctx.covering_node();
13 if !valid_covering_node(node) {
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 {
22 ctx.build("introduce variable", move |edit| {
23 let mut buf = String::new();
25 let cursor_offset = if wrap_in_block {
26 buf.push_str("{ let var_name = ");
27 TextUnit::of_str("{ let ")
29 buf.push_str("let var_name = ");
30 TextUnit::of_str("let ")
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())
41 if !full_stmt.unwrap().has_semi() {
44 edit.replace(expr.syntax().range(), buf);
47 indent.text().push_to(&mut buf);
48 edit.target(expr.syntax().range());
49 edit.replace(expr.syntax().range(), "var_name".to_string());
50 edit.insert(anchor_stmt.range().start(), buf);
52 edit.insert(anchor_stmt.range().end(), " }");
55 edit.set_cursor(anchor_stmt.range().start() + cursor_offset);
59 fn valid_covering_node(node: &SyntaxNode) -> bool {
60 node.kind() != COMMENT
62 /// Check whether the node is a valid expression which can be extracted to a variable.
63 /// In general that's true for any expression, but in some cases that would produce invalid code.
64 fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> {
67 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
68 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
69 LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
70 _ => ast::Expr::cast(node),
74 /// Returns the syntax node which will follow the freshly introduced var
75 /// and a boolean indicating whether we have to wrap it within a { } block
76 /// to produce correct code.
77 /// It can be a statement, the last in a block expression or a wanna be block
78 /// expression like a lambda or match arm.
79 fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
80 expr.syntax().ancestors().find_map(|node| {
81 if ast::Stmt::cast(node).is_some() {
82 return Some((node, false));
85 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
86 if expr.syntax() == node {
87 return Some((node, false));
91 if let Some(parent) = node.parent() {
92 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
93 return Some((node, true));
104 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range, check_assist_target, check_assist_range_target};
107 fn test_introduce_var_simple() {
116 let <|>var_name = 1 + 1;
123 fn test_introduce_var_expr_stmt() {
132 let <|>var_name = 1 + 1;
138 fn test_introduce_var_part_of_expr_stmt() {
154 fn test_introduce_var_last_expr() {
163 let <|>var_name = 1 + 1;
170 fn test_introduce_var_last_full_expr() {
179 let <|>var_name = bar(1 + 1);
186 fn test_introduce_var_block_expr_second_to_last() {
191 <|>{ let x = 0; x }<|>
196 let <|>var_name = { let x = 0; x };
203 fn test_introduce_var_in_match_arm_no_block() {
209 let tuple = match x {
210 true => (<|>2 + 2<|>, true)
218 let tuple = match x {
219 true => { let <|>var_name = 2 + 2; (var_name, true) }
228 fn test_introduce_var_in_match_arm_with_block() {
234 let tuple = match x {
246 let tuple = match x {
249 let <|>var_name = 2 + y;
260 fn test_introduce_var_in_closure_no_block() {
265 let lambda = |x: u32| <|>x * 2<|>;
270 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
277 fn test_introduce_var_in_closure_with_block() {
282 let lambda = |x: u32| { <|>x * 2<|> };
287 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
294 fn test_introduce_var_path_simple() {
299 let o = S<|>ome(true);
304 let <|>var_name = Some(true);
312 fn test_introduce_var_path_method() {
317 let v = b<|>ar.foo();
322 let <|>var_name = bar.foo();
330 fn test_introduce_var_return() {
340 let <|>var_name = 2 + 2;
348 fn test_introduce_var_break() {
361 let <|>var_name = 2 + 2;
370 fn test_introduce_var_for_cast() {
375 let v = 0f32 a<|>s u32;
380 let <|>var_name = 0f32 as u32;
388 fn test_introduce_var_for_return_not_applicable() {
389 check_assist_not_applicable(
400 fn test_introduce_var_for_break_not_applicable() {
401 check_assist_not_applicable(
414 fn test_introduce_var_in_comment_not_applicable() {
415 check_assist_not_applicable(
420 let tuple = match x {
422 true => (2 + 2, true)
430 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
432 fn introduce_var_target() {
443 check_assist_range_target(
448 let tuple = match x {
449 true => (<|>2 + 2<|>, true)