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.replace(expr.syntax().range(), "var_name".to_string());
49 edit.insert(anchor_stmt.range().start(), buf);
51 edit.insert(anchor_stmt.range().end(), " }");
54 edit.set_cursor(anchor_stmt.range().start() + cursor_offset);
58 fn valid_covering_node(node: &SyntaxNode) -> bool {
59 node.kind() != COMMENT
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.
63 fn valid_target_expr(node: &SyntaxNode) -> Option<&ast::Expr> {
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),
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.
78 fn 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));
84 if let Some(expr) = node.parent().and_then(ast::Block::cast).and_then(|it| it.expr()) {
85 if expr.syntax() == node {
86 return Some((node, false));
90 if let Some(parent) = node.parent() {
91 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
92 return Some((node, true));
103 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range};
106 fn test_introduce_var_simple() {
115 let <|>var_name = 1 + 1;
122 fn test_introduce_var_expr_stmt() {
131 let <|>var_name = 1 + 1;
137 fn test_introduce_var_part_of_expr_stmt() {
153 fn test_introduce_var_last_expr() {
162 let <|>var_name = 1 + 1;
169 fn test_introduce_var_last_full_expr() {
178 let <|>var_name = bar(1 + 1);
185 fn test_introduce_var_block_expr_second_to_last() {
190 <|>{ let x = 0; x }<|>
195 let <|>var_name = { let x = 0; x };
202 fn test_introduce_var_in_match_arm_no_block() {
208 let tuple = match x {
209 true => (<|>2 + 2<|>, true)
217 let tuple = match x {
218 true => { let <|>var_name = 2 + 2; (var_name, true) }
227 fn test_introduce_var_in_match_arm_with_block() {
233 let tuple = match x {
245 let tuple = match x {
248 let <|>var_name = 2 + y;
259 fn test_introduce_var_in_closure_no_block() {
264 let lambda = |x: u32| <|>x * 2<|>;
269 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
276 fn test_introduce_var_in_closure_with_block() {
281 let lambda = |x: u32| { <|>x * 2<|> };
286 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
293 fn test_introduce_var_path_simple() {
298 let o = S<|>ome(true);
303 let <|>var_name = Some(true);
311 fn test_introduce_var_path_method() {
316 let v = b<|>ar.foo();
321 let <|>var_name = bar.foo();
329 fn test_introduce_var_return() {
339 let <|>var_name = 2 + 2;
347 fn test_introduce_var_break() {
360 let <|>var_name = 2 + 2;
369 fn test_introduce_var_for_cast() {
374 let v = 0f32 a<|>s u32;
379 let <|>var_name = 0f32 as u32;
387 fn test_introduce_var_for_return_not_applicable() {
388 check_assist_not_applicable(
399 fn test_introduce_var_for_break_not_applicable() {
400 check_assist_not_applicable(
413 fn test_introduce_var_in_comment_not_applicable() {
414 check_assist_not_applicable(
419 let tuple = match x {
421 true => (2 + 2, true)