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);
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);
74 fn valid_covering_node(node: &SyntaxNode) -> bool {
75 node.kind() != COMMENT
77 /// Check whether the node is a valid expression which can be extracted to a variable.
78 /// In general that's true for any expression, but in some cases that would produce invalid code.
79 fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> {
82 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
83 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
84 LOOP_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
85 _ => ast::Expr::cast(node),
89 /// Returns the syntax node which will follow the freshly introduced var
90 /// and a boolean indicating whether we have to wrap it within a { } block
91 /// to produce correct code.
92 /// It can be a statement, the last in a block expression or a wanna be block
93 /// expression like a lambda or match arm.
94 fn anchor_stmt(expr: &ast::Expr) -> Option<(&SyntaxNode, bool)> {
95 expr.syntax().ancestors().find_map(|node| {
96 if ast::Stmt::cast(node).is_some() {
97 return Some((node, false));
100 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
101 if expr.syntax() == node {
102 return Some((node, false));
106 if let Some(parent) = node.parent() {
107 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
108 return Some((node, true));
119 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range, check_assist_target, check_assist_range_target};
122 fn test_introduce_var_simple() {
131 let <|>var_name = 1 + 1;
138 fn test_introduce_var_expr_stmt() {
147 let <|>var_name = 1 + 1;
153 fn test_introduce_var_part_of_expr_stmt() {
169 fn test_introduce_var_last_expr() {
178 let <|>var_name = 1 + 1;
185 fn test_introduce_var_last_full_expr() {
194 let <|>var_name = bar(1 + 1);
201 fn test_introduce_var_block_expr_second_to_last() {
206 <|>{ let x = 0; x }<|>
211 let <|>var_name = { let x = 0; x };
218 fn test_introduce_var_in_match_arm_no_block() {
224 let tuple = match x {
225 true => (<|>2 + 2<|>, true)
233 let tuple = match x {
234 true => { let <|>var_name = 2 + 2; (var_name, true) }
243 fn test_introduce_var_in_match_arm_with_block() {
249 let tuple = match x {
261 let tuple = match x {
264 let <|>var_name = 2 + y;
275 fn test_introduce_var_in_closure_no_block() {
280 let lambda = |x: u32| <|>x * 2<|>;
285 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
292 fn test_introduce_var_in_closure_with_block() {
297 let lambda = |x: u32| { <|>x * 2<|> };
302 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
309 fn test_introduce_var_path_simple() {
314 let o = S<|>ome(true);
319 let <|>var_name = Some(true);
327 fn test_introduce_var_path_method() {
332 let v = b<|>ar.foo();
337 let <|>var_name = bar.foo();
345 fn test_introduce_var_return() {
355 let <|>var_name = 2 + 2;
363 fn test_introduce_var_does_not_add_extra_whitespace() {
377 let <|>var_name = 2 + 2;
394 let <|>var_name = 2 + 2;
419 let <|>var_name = 2 + 2;
427 fn test_introduce_var_break() {
440 let <|>var_name = 2 + 2;
449 fn test_introduce_var_for_cast() {
454 let v = 0f32 a<|>s u32;
459 let <|>var_name = 0f32 as u32;
467 fn test_introduce_var_for_return_not_applicable() {
468 check_assist_not_applicable(
479 fn test_introduce_var_for_break_not_applicable() {
480 check_assist_not_applicable(
493 fn test_introduce_var_in_comment_not_applicable() {
494 check_assist_not_applicable(
499 let tuple = match x {
501 true => (2 + 2, true)
509 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
511 fn introduce_var_target() {
522 check_assist_range_target(
527 let tuple = match x {
528 true => (<|>2 + 2<|>, true)