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
86 .and_then(ast::Block::cast)
87 .and_then(|it| it.expr())
89 if expr.syntax() == node {
90 return Some((node, false));
94 if let Some(parent) = node.parent() {
95 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
96 return Some((node, true));
107 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_range};
110 fn test_introduce_var_simple() {
119 let <|>var_name = 1 + 1;
126 fn test_introduce_var_expr_stmt() {
135 let <|>var_name = 1 + 1;
141 fn test_introduce_var_part_of_expr_stmt() {
157 fn test_introduce_var_last_expr() {
166 let <|>var_name = 1 + 1;
173 fn test_introduce_var_last_full_expr() {
182 let <|>var_name = bar(1 + 1);
189 fn test_introduce_var_block_expr_second_to_last() {
194 <|>{ let x = 0; x }<|>
199 let <|>var_name = { let x = 0; x };
206 fn test_introduce_var_in_match_arm_no_block() {
212 let tuple = match x {
213 true => (<|>2 + 2<|>, true)
221 let tuple = match x {
222 true => { let <|>var_name = 2 + 2; (var_name, true) }
231 fn test_introduce_var_in_match_arm_with_block() {
237 let tuple = match x {
249 let tuple = match x {
252 let <|>var_name = 2 + y;
263 fn test_introduce_var_in_closure_no_block() {
268 let lambda = |x: u32| <|>x * 2<|>;
273 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
280 fn test_introduce_var_in_closure_with_block() {
285 let lambda = |x: u32| { <|>x * 2<|> };
290 let lambda = |x: u32| { let <|>var_name = x * 2; var_name };
297 fn test_introduce_var_path_simple() {
302 let o = S<|>ome(true);
307 let <|>var_name = Some(true);
315 fn test_introduce_var_path_method() {
320 let v = b<|>ar.foo();
325 let <|>var_name = bar.foo();
333 fn test_introduce_var_return() {
343 let <|>var_name = 2 + 2;
351 fn test_introduce_var_break() {
364 let <|>var_name = 2 + 2;
373 fn test_introduce_var_for_cast() {
378 let v = 0f32 a<|>s u32;
383 let <|>var_name = 0f32 as u32;
391 fn test_introduce_var_for_return_not_applicable() {
392 check_assist_not_applicable(
403 fn test_introduce_var_for_break_not_applicable() {
404 check_assist_not_applicable(
417 fn test_introduce_var_in_comment_not_applicable() {
418 check_assist_not_applicable(
423 let tuple = match x {
425 true => (2 + 2, true)