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(mut 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.add_action("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);
48 // We want to maintain the indent level,
49 // but we do not want to duplicate possible
50 // extra newlines in the indent block
51 for chunk in indent.text().chunks() {
52 if chunk.starts_with("\r\n") {
54 buf.push_str(chunk.trim_start_matches("\r\n"));
55 } else if chunk.starts_with("\n") {
57 buf.push_str(chunk.trim_start_matches("\n"));
63 edit.target(expr.syntax().range());
64 edit.replace(expr.syntax().range(), "var_name".to_string());
65 edit.insert(anchor_stmt.range().start(), buf);
67 edit.insert(anchor_stmt.range().end(), " }");
70 edit.set_cursor(anchor_stmt.range().start() + cursor_offset);
76 fn valid_covering_node(node: &SyntaxNode) -> bool {
77 node.kind() != COMMENT
79 /// Check whether the node is a valid expression which can be extracted to a variable.
80 /// In general that's true for any expression, but in some cases that would produce invalid code.
81 fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> {
84 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
85 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
86 LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
87 _ => ast::Expr::cast(node),
91 /// Returns the syntax node which will follow the freshly introduced var
92 /// and a boolean indicating whether we have to wrap it within a { } block
93 /// to produce correct code.
94 /// It can be a statement, the last in a block expression or a wanna be block
95 /// expression like a lambda or match arm.
96 fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
97 expr.syntax().ancestors().find_map(|node| {
98 if ast::Stmt::cast(node).is_some() {
99 return Some((node, false));
102 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
103 if expr.syntax() == node {
104 return Some((node, false));
108 if let Some(parent) = node.parent() {
109 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
110 return Some((node, true));
121 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range, check_assist_target, check_assist_range_target};
124 fn test_introduce_var_simple() {
133 let <|>var_name = 1 + 1;
140 fn test_introduce_var_expr_stmt() {
149 let <|>var_name = 1 + 1;
155 fn test_introduce_var_part_of_expr_stmt() {
171 fn test_introduce_var_last_expr() {
180 let <|>var_name = 1 + 1;
187 fn test_introduce_var_last_full_expr() {
196 let <|>var_name = bar(1 + 1);
203 fn test_introduce_var_block_expr_second_to_last() {
208 <|>{ let x = 0; x }<|>
213 let <|>var_name = { let x = 0; x };
220 fn test_introduce_var_in_match_arm_no_block() {
226 let tuple = match x {
227 true => (<|>2 + 2<|>, true)
235 let tuple = match x {
236 true => { let <|>var_name = 2 + 2; (var_name, true) }
245 fn test_introduce_var_in_match_arm_with_block() {
251 let tuple = match x {
263 let tuple = match x {
266 let <|>var_name = 2 + y;
277 fn test_introduce_var_in_closure_no_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_in_closure_with_block() {
299 let lambda = |x: u32| { <|>x * 2<|> };
304 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
311 fn test_introduce_var_path_simple() {
316 let o = S<|>ome(true);
321 let <|>var_name = Some(true);
329 fn test_introduce_var_path_method() {
334 let v = b<|>ar.foo();
339 let <|>var_name = bar.foo();
347 fn test_introduce_var_return() {
357 let <|>var_name = 2 + 2;
365 fn test_introduce_var_does_not_add_extra_whitespace() {
379 let <|>var_name = 2 + 2;
396 let <|>var_name = 2 + 2;
421 let <|>var_name = 2 + 2;
429 fn test_introduce_var_break() {
442 let <|>var_name = 2 + 2;
451 fn test_introduce_var_for_cast() {
456 let v = 0f32 a<|>s u32;
461 let <|>var_name = 0f32 as u32;
469 fn test_introduce_var_for_return_not_applicable() {
470 check_assist_not_applicable(
481 fn test_introduce_var_for_break_not_applicable() {
482 check_assist_not_applicable(
495 fn test_introduce_var_in_comment_not_applicable() {
496 check_assist_not_applicable(
501 let tuple = match x {
503 true => (2 + 2, true)
511 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
513 fn introduce_var_target() {
524 check_assist_range_target(
529 let tuple = match x {
530 true => (<|>2 + 2<|>, true)