| use hir::{HirDisplay, TypeInfo}; |
| use ide_db::{ |
| assists::GroupLabel, |
| syntax_helpers::{LexedStr, suggest_name}, |
| }; |
| use syntax::{ |
| NodeOrToken, SyntaxKind, SyntaxNode, T, |
| algo::ancestors_at_offset, |
| ast::{ |
| self, AstNode, |
| edit::{AstNodeEdit, IndentLevel}, |
| make, |
| syntax_factory::SyntaxFactory, |
| }, |
| syntax_editor::Position, |
| }; |
| |
| use crate::{AssistContext, AssistId, Assists, utils::is_body_const}; |
| |
| // Assist: extract_variable |
| // |
| // Extracts subexpression into a variable. |
| // |
| // ``` |
| // fn main() { |
| // $0(1 + 2)$0 * 4; |
| // } |
| // ``` |
| // -> |
| // ``` |
| // fn main() { |
| // let $0var_name = 1 + 2; |
| // var_name * 4; |
| // } |
| // ``` |
| |
| // Assist: extract_constant |
| // |
| // Extracts subexpression into a constant. |
| // |
| // ``` |
| // fn main() { |
| // $0(1 + 2)$0 * 4; |
| // } |
| // ``` |
| // -> |
| // ``` |
| // fn main() { |
| // const $0VAR_NAME: i32 = 1 + 2; |
| // VAR_NAME * 4; |
| // } |
| // ``` |
| |
| // Assist: extract_static |
| // |
| // Extracts subexpression into a static. |
| // |
| // ``` |
| // fn main() { |
| // $0(1 + 2)$0 * 4; |
| // } |
| // ``` |
| // -> |
| // ``` |
| // fn main() { |
| // static $0VAR_NAME: i32 = 1 + 2; |
| // VAR_NAME * 4; |
| // } |
| // ``` |
| pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { |
| let node = if ctx.has_empty_selection() { |
| if let Some(t) = ctx.token_at_offset().find(|it| it.kind() == T![;]) { |
| t.parent().and_then(ast::ExprStmt::cast)?.syntax().clone() |
| } else if let Some(expr) = ancestors_at_offset(ctx.source_file().syntax(), ctx.offset()) |
| .next() |
| .and_then(ast::Expr::cast) |
| { |
| expr.syntax().ancestors().find_map(valid_target_expr)?.syntax().clone() |
| } else { |
| return None; |
| } |
| } else { |
| match ctx.covering_element() { |
| NodeOrToken::Node(it) => it, |
| NodeOrToken::Token(it) if it.kind() == SyntaxKind::COMMENT => { |
| cov_mark::hit!(extract_var_in_comment_is_not_applicable); |
| return None; |
| } |
| NodeOrToken::Token(it) => it.parent()?, |
| } |
| }; |
| |
| let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?; |
| let range = node.text_range(); |
| |
| let to_extract = node |
| .descendants() |
| .take_while(|it| range.contains_range(it.text_range())) |
| .find_map(valid_target_expr)?; |
| |
| let ty = ctx.sema.type_of_expr(&to_extract).map(TypeInfo::adjusted); |
| if matches!(&ty, Some(ty_info) if ty_info.is_unit()) { |
| return None; |
| } |
| |
| let parent = to_extract.syntax().parent().and_then(ast::Expr::cast); |
| // Any expression that autoderefs may need adjustment. |
| let mut needs_adjust = parent.as_ref().is_some_and(|it| match it { |
| ast::Expr::FieldExpr(_) |
| | ast::Expr::MethodCallExpr(_) |
| | ast::Expr::CallExpr(_) |
| | ast::Expr::AwaitExpr(_) => true, |
| ast::Expr::IndexExpr(index) if index.base().as_ref() == Some(&to_extract) => true, |
| _ => false, |
| }); |
| let mut to_extract_no_ref = peel_parens(to_extract.clone()); |
| let needs_ref = needs_adjust |
| && match &to_extract_no_ref { |
| ast::Expr::FieldExpr(_) |
| | ast::Expr::IndexExpr(_) |
| | ast::Expr::MacroExpr(_) |
| | ast::Expr::ParenExpr(_) |
| | ast::Expr::PathExpr(_) => true, |
| ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::UnaryOp::Deref) => { |
| to_extract_no_ref = prefix.expr()?; |
| needs_adjust = false; |
| false |
| } |
| _ => false, |
| }; |
| let module = ctx.sema.scope(to_extract.syntax())?.module(); |
| let target = to_extract.syntax().text_range(); |
| let needs_mut = match &parent { |
| Some(ast::Expr::RefExpr(expr)) => expr.mut_token().is_some(), |
| _ => needs_adjust && !needs_ref && ty.as_ref().is_some_and(|ty| ty.is_mutable_reference()), |
| }; |
| for kind in ExtractionKind::ALL { |
| let Some(anchor) = Anchor::from(&to_extract, kind) else { |
| continue; |
| }; |
| |
| let ty_string = match kind { |
| ExtractionKind::Constant | ExtractionKind::Static => { |
| let Some(ty) = ty.clone() else { |
| continue; |
| }; |
| |
| // We can't mutably reference a const, nor can we define |
| // one using a non-const expression or one of unknown type |
| if needs_mut |
| || !is_body_const(&ctx.sema, &to_extract_no_ref) |
| || ty.is_unknown() |
| || ty.is_mutable_reference() |
| { |
| continue; |
| } |
| |
| let Ok(type_string) = ty.display_source_code(ctx.db(), module.into(), false) else { |
| continue; |
| }; |
| |
| type_string |
| } |
| _ => "".to_owned(), |
| }; |
| |
| acc.add_group( |
| &GroupLabel("Extract into...".to_owned()), |
| kind.assist_id(), |
| kind.label(), |
| target, |
| |edit| { |
| let (var_name, expr_replace) = kind.get_name_and_expr(ctx, &to_extract); |
| |
| let make = SyntaxFactory::with_mappings(); |
| let mut editor = edit.make_editor(&expr_replace); |
| |
| let pat_name = make.name(&var_name); |
| let name_expr = make.expr_path(make::ext::ident_path(&var_name)); |
| |
| if let Some(cap) = ctx.config.snippet_cap { |
| let tabstop = edit.make_tabstop_before(cap); |
| editor.add_annotation(pat_name.syntax().clone(), tabstop); |
| } |
| |
| let initializer = match ty.as_ref().filter(|_| needs_ref) { |
| Some(receiver_type) if receiver_type.is_mutable_reference() => { |
| make.expr_ref(to_extract_no_ref.clone(), true) |
| } |
| Some(receiver_type) if receiver_type.is_reference() => { |
| make.expr_ref(to_extract_no_ref.clone(), false) |
| } |
| _ => to_extract_no_ref.clone(), |
| }; |
| |
| let new_stmt: ast::Stmt = match kind { |
| ExtractionKind::Variable => { |
| let ident_pat = make.ident_pat(false, needs_mut, pat_name); |
| make.let_stmt(ident_pat.into(), None, Some(initializer)).into() |
| } |
| ExtractionKind::Constant => { |
| let ast_ty = make.ty(&ty_string); |
| ast::Item::Const(make.item_const(None, None, pat_name, ast_ty, initializer)) |
| .into() |
| } |
| ExtractionKind::Static => { |
| let ast_ty = make.ty(&ty_string); |
| ast::Item::Static(make.item_static( |
| None, |
| false, |
| false, |
| pat_name, |
| ast_ty, |
| Some(initializer), |
| )) |
| .into() |
| } |
| }; |
| |
| match &anchor { |
| Anchor::Before(place) => { |
| let prev_ws = place.prev_sibling_or_token().and_then(|it| it.into_token()); |
| let indent_to = IndentLevel::from_node(place); |
| |
| // Adjust ws to insert depending on if this is all inline or on separate lines |
| let trailing_ws = if prev_ws.is_some_and(|it| it.text().starts_with('\n')) { |
| format!("\n{indent_to}") |
| } else { |
| " ".to_owned() |
| }; |
| |
| editor.insert_all( |
| Position::before(place), |
| vec![ |
| new_stmt.syntax().clone().into(), |
| make::tokens::whitespace(&trailing_ws).into(), |
| ], |
| ); |
| |
| editor.replace(expr_replace, name_expr.syntax()); |
| } |
| Anchor::Replace(stmt) => { |
| cov_mark::hit!(test_extract_var_expr_stmt); |
| |
| editor.replace(stmt.syntax(), new_stmt.syntax()); |
| } |
| Anchor::WrapInBlock(to_wrap) => { |
| let indent_to = to_wrap.indent_level(); |
| |
| let block = if to_wrap.syntax() == &expr_replace { |
| // Since `expr_replace` is the same that needs to be wrapped in a block, |
| // we can just directly replace it with a block |
| make.block_expr([new_stmt], Some(name_expr)) |
| } else { |
| // `expr_replace` is a descendant of `to_wrap`, so we just replace it with `name_expr`. |
| editor.replace(expr_replace, name_expr.syntax()); |
| make.block_expr([new_stmt], Some(to_wrap.clone())) |
| } |
| // fixup indentation of block |
| .indent_with_mapping(indent_to, &make); |
| |
| editor.replace(to_wrap.syntax(), block.syntax()); |
| } |
| } |
| |
| editor.add_mappings(make.finish_with_mappings()); |
| edit.add_file_edits(ctx.vfs_file_id(), editor); |
| edit.rename(); |
| }, |
| ); |
| } |
| |
| Some(()) |
| } |
| |
| fn peel_parens(mut expr: ast::Expr) -> ast::Expr { |
| while let ast::Expr::ParenExpr(parens) = &expr { |
| let Some(expr_inside) = parens.expr() else { break }; |
| expr = expr_inside; |
| } |
| expr |
| } |
| |
| /// Check whether the node is a valid expression which can be extracted to a variable. |
| /// In general that's true for any expression, but in some cases that would produce invalid code. |
| fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> { |
| match node.kind() { |
| SyntaxKind::PATH_EXPR | SyntaxKind::LOOP_EXPR => None, |
| SyntaxKind::BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()), |
| SyntaxKind::RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()), |
| SyntaxKind::BLOCK_EXPR => { |
| ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from) |
| } |
| _ => ast::Expr::cast(node), |
| } |
| } |
| |
| enum ExtractionKind { |
| Variable, |
| Constant, |
| Static, |
| } |
| |
| impl ExtractionKind { |
| const ALL: &'static [ExtractionKind] = |
| &[ExtractionKind::Variable, ExtractionKind::Constant, ExtractionKind::Static]; |
| |
| fn assist_id(&self) -> AssistId { |
| let s = match self { |
| ExtractionKind::Variable => "extract_variable", |
| ExtractionKind::Constant => "extract_constant", |
| ExtractionKind::Static => "extract_static", |
| }; |
| |
| AssistId::refactor_extract(s) |
| } |
| |
| fn label(&self) -> &'static str { |
| match self { |
| ExtractionKind::Variable => "Extract into variable", |
| ExtractionKind::Constant => "Extract into constant", |
| ExtractionKind::Static => "Extract into static", |
| } |
| } |
| |
| fn get_name_and_expr( |
| &self, |
| ctx: &AssistContext<'_>, |
| to_extract: &ast::Expr, |
| ) -> (String, SyntaxNode) { |
| // We only do this sort of extraction for fields because they should have lowercase names |
| if let ExtractionKind::Variable = self { |
| let field_shorthand = to_extract |
| .syntax() |
| .parent() |
| .and_then(ast::RecordExprField::cast) |
| .filter(|field| field.name_ref().is_some()); |
| |
| if let Some(field) = field_shorthand { |
| return (field.to_string(), field.syntax().clone()); |
| } |
| } |
| |
| let mut name_generator = |
| suggest_name::NameGenerator::new_from_scope_locals(ctx.sema.scope(to_extract.syntax())); |
| let var_name = if let Some(literal_name) = get_literal_name(ctx, to_extract) { |
| name_generator.suggest_name(&literal_name) |
| } else { |
| name_generator.for_variable(to_extract, &ctx.sema) |
| }; |
| |
| let var_name = match self { |
| ExtractionKind::Variable => var_name.to_lowercase(), |
| ExtractionKind::Constant | ExtractionKind::Static => var_name.to_uppercase(), |
| }; |
| |
| (var_name, to_extract.syntax().clone()) |
| } |
| } |
| |
| fn get_literal_name(ctx: &AssistContext<'_>, expr: &ast::Expr) -> Option<String> { |
| let ast::Expr::Literal(literal) = expr else { |
| return None; |
| }; |
| |
| let inner = match literal.kind() { |
| ast::LiteralKind::String(string) => string.value().ok()?.into_owned(), |
| ast::LiteralKind::ByteString(byte_string) => { |
| String::from_utf8(byte_string.value().ok()?.into_owned()).ok()? |
| } |
| ast::LiteralKind::CString(cstring) => { |
| String::from_utf8(cstring.value().ok()?.into_owned()).ok()? |
| } |
| _ => return None, |
| }; |
| |
| // Entirely arbitrary |
| if inner.len() > 32 { |
| return None; |
| } |
| |
| match LexedStr::single_token(ctx.edition(), &inner) { |
| Some((SyntaxKind::IDENT, None)) => Some(inner), |
| _ => None, |
| } |
| } |
| |
| #[derive(Debug, Clone)] |
| enum Anchor { |
| Before(SyntaxNode), |
| Replace(ast::ExprStmt), |
| WrapInBlock(ast::Expr), |
| } |
| |
| impl Anchor { |
| fn from(to_extract: &ast::Expr, kind: &ExtractionKind) -> Option<Anchor> { |
| let result = to_extract |
| .syntax() |
| .ancestors() |
| .take_while(|it| !ast::Item::can_cast(it.kind()) || ast::MacroCall::can_cast(it.kind())) |
| .find_map(|node| { |
| if ast::MacroCall::can_cast(node.kind()) { |
| return None; |
| } |
| if let Some(expr) = |
| node.parent().and_then(ast::StmtList::cast).and_then(|it| it.tail_expr()) |
| && expr.syntax() == &node |
| { |
| cov_mark::hit!(test_extract_var_last_expr); |
| return Some(Anchor::Before(node)); |
| } |
| |
| if let Some(parent) = node.parent() { |
| if let Some(parent) = ast::ClosureExpr::cast(parent.clone()) { |
| cov_mark::hit!(test_extract_var_in_closure_no_block); |
| return parent.body().map(Anchor::WrapInBlock); |
| } |
| if let Some(parent) = ast::MatchArm::cast(parent) { |
| if node.kind() == SyntaxKind::MATCH_GUARD { |
| cov_mark::hit!(test_extract_var_in_match_guard); |
| } else { |
| cov_mark::hit!(test_extract_var_in_match_arm_no_block); |
| return parent.expr().map(Anchor::WrapInBlock); |
| } |
| } |
| } |
| |
| if let Some(stmt) = ast::Stmt::cast(node.clone()) { |
| if let ast::Stmt::ExprStmt(stmt) = stmt |
| && stmt.expr().as_ref() == Some(to_extract) |
| { |
| return Some(Anchor::Replace(stmt)); |
| } |
| return Some(Anchor::Before(node)); |
| } |
| None |
| }); |
| |
| match kind { |
| ExtractionKind::Constant | ExtractionKind::Static if result.is_none() => { |
| to_extract.syntax().ancestors().find_map(|node| { |
| let item = ast::Item::cast(node.clone())?; |
| let parent = item.syntax().parent()?; |
| match parent.kind() { |
| SyntaxKind::ITEM_LIST |
| | SyntaxKind::SOURCE_FILE |
| | SyntaxKind::ASSOC_ITEM_LIST |
| | SyntaxKind::STMT_LIST => Some(Anchor::Before(node)), |
| _ => None, |
| } |
| }) |
| } |
| _ => result, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| // NOTE: We use check_assist_by_label, but not check_assist_not_applicable_by_label |
| // because all of our not-applicable tests should behave that way for both assists |
| // extract_variable offers, and check_assist_not_applicable ensures neither is offered |
| use crate::tests::{ |
| check_assist_by_label, check_assist_not_applicable, check_assist_not_applicable_by_label, |
| check_assist_target, |
| }; |
| |
| use super::*; |
| |
| #[test] |
| fn extract_var_simple_without_select() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() -> i32 { |
| if$0 true { |
| 1 |
| } else { |
| 2 |
| } |
| } |
| "#, |
| r#" |
| fn main() -> i32 { |
| let $0var_name = if true { |
| 1 |
| } else { |
| 2 |
| }; |
| var_name |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() -> i32 { 1 } |
| fn main() { |
| foo();$0 |
| } |
| "#, |
| r#" |
| fn foo() -> i32 { 1 } |
| fn main() { |
| let $0foo = foo(); |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let a = Some(2); |
| a.is_some();$0 |
| } |
| "#, |
| r#" |
| fn main() { |
| let a = Some(2); |
| let $0is_some = a.is_some(); |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| "hello"$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| let $0hello = "hello"; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| 1 + 2$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| let $0var_name = 1 + 2; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| match () { |
| () if true => 1, |
| _ => 2, |
| };$0 |
| } |
| "#, |
| r#" |
| fn main() { |
| let $0var_name = match () { |
| () if true => 1, |
| _ => 2, |
| }; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_const_simple_without_select() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() -> i32 { |
| if$0 true { |
| 1 |
| } else { |
| 2 |
| } |
| } |
| "#, |
| r#" |
| fn main() -> i32 { |
| const $0VAR_NAME: i32 = if true { |
| 1 |
| } else { |
| 2 |
| }; |
| VAR_NAME |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| const fn foo() -> i32 { 1 } |
| fn main() { |
| foo();$0 |
| } |
| "#, |
| r#" |
| const fn foo() -> i32 { 1 } |
| fn main() { |
| const $0FOO: i32 = foo(); |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| "hello"$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| const $0HELLO: &'static str = "hello"; |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| 1 + 2$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| const $0VAR_NAME: i32 = 1 + 2; |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| match () { |
| () if true => 1, |
| _ => 2, |
| };$0 |
| } |
| "#, |
| r#" |
| fn main() { |
| const $0VAR_NAME: i32 = match () { |
| () if true => 1, |
| _ => 2, |
| }; |
| } |
| "#, |
| "Extract into constant", |
| ); |
| } |
| |
| #[test] |
| fn extract_static_simple_without_select() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() -> i32 { |
| if$0 true { |
| 1 |
| } else { |
| 2 |
| } |
| } |
| "#, |
| r#" |
| fn main() -> i32 { |
| static $0VAR_NAME: i32 = if true { |
| 1 |
| } else { |
| 2 |
| }; |
| VAR_NAME |
| } |
| "#, |
| "Extract into static", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| const fn foo() -> i32 { 1 } |
| fn main() { |
| foo();$0 |
| } |
| "#, |
| r#" |
| const fn foo() -> i32 { 1 } |
| fn main() { |
| static $0FOO: i32 = foo(); |
| } |
| "#, |
| "Extract into static", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| "hello"$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| static $0HELLO: &'static str = "hello"; |
| } |
| "#, |
| "Extract into static", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| 1 + 2$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| static $0VAR_NAME: i32 = 1 + 2; |
| } |
| "#, |
| "Extract into static", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| match () { |
| () if true => 1, |
| _ => 2, |
| };$0 |
| } |
| "#, |
| r#" |
| fn main() { |
| static $0VAR_NAME: i32 = match () { |
| () if true => 1, |
| _ => 2, |
| }; |
| } |
| "#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn dont_extract_unit_expr_without_select() { |
| check_assist_not_applicable( |
| extract_variable, |
| r#" |
| fn foo() {} |
| fn main() { |
| foo()$0; |
| } |
| "#, |
| ); |
| |
| check_assist_not_applicable( |
| extract_variable, |
| r#" |
| fn foo() { |
| let mut i = 3; |
| if i >= 0 { |
| i += 1; |
| } else { |
| i -= 1; |
| }$0 |
| }"#, |
| ); |
| } |
| |
| #[test] |
| fn extract_var_simple() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| foo($01 + 1$0); |
| }"#, |
| r#" |
| fn foo() { |
| let $0var_name = 1 + 1; |
| foo(var_name); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_const_simple() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| foo($01 + 1$0); |
| }"#, |
| r#" |
| fn foo() { |
| const $0VAR_NAME: i32 = 1 + 1; |
| foo(VAR_NAME); |
| }"#, |
| "Extract into constant", |
| ); |
| } |
| |
| #[test] |
| fn extract_static_simple() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| foo($01 + 1$0); |
| }"#, |
| r#" |
| fn foo() { |
| static $0VAR_NAME: i32 = 1 + 1; |
| foo(VAR_NAME); |
| }"#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn dont_extract_in_comment() { |
| cov_mark::check!(extract_var_in_comment_is_not_applicable); |
| check_assist_not_applicable(extract_variable, r#"fn main() { 1 + /* $0comment$0 */ 1; }"#); |
| } |
| |
| #[test] |
| fn extract_var_expr_stmt() { |
| cov_mark::check!(test_extract_var_expr_stmt); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $0 1 + 1$0; |
| }"#, |
| r#" |
| fn foo() { |
| let $0var_name = 1 + 1; |
| }"#, |
| "Extract into variable", |
| ); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $0{ let x = 0; x }$0; |
| something_else(); |
| }"#, |
| r#" |
| fn foo() { |
| let $0var_name = { let x = 0; x }; |
| something_else(); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_const_expr_stmt() { |
| cov_mark::check!(test_extract_var_expr_stmt); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $0 1 + 1$0; |
| }"#, |
| r#" |
| fn foo() { |
| const $0VAR_NAME: i32 = 1 + 1; |
| }"#, |
| "Extract into constant", |
| ); |
| // This is hilarious but as far as I know, it's valid |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $0{ let x = 0; x }$0; |
| something_else(); |
| }"#, |
| r#" |
| fn foo() { |
| const $0VAR_NAME: i32 = { let x = 0; x }; |
| something_else(); |
| }"#, |
| "Extract into constant", |
| ); |
| } |
| |
| #[test] |
| fn extract_static_expr_stmt() { |
| cov_mark::check!(test_extract_var_expr_stmt); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $0 1 + 1$0; |
| }"#, |
| r#" |
| fn foo() { |
| static $0VAR_NAME: i32 = 1 + 1; |
| }"#, |
| "Extract into static", |
| ); |
| // This is hilarious but as far as I know, it's valid |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $0{ let x = 0; x }$0; |
| something_else(); |
| }"#, |
| r#" |
| fn foo() { |
| static $0VAR_NAME: i32 = { let x = 0; x }; |
| something_else(); |
| }"#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_part_of_expr_stmt() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $01$0 + 1; |
| }"#, |
| r#" |
| fn foo() { |
| let $0var_name = 1; |
| var_name + 1; |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_const_part_of_expr_stmt() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $01$0 + 1; |
| }"#, |
| r#" |
| fn foo() { |
| const $0VAR_NAME: i32 = 1; |
| VAR_NAME + 1; |
| }"#, |
| "Extract into constant", |
| ); |
| } |
| |
| #[test] |
| fn extract_static_part_of_expr_stmt() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| $01$0 + 1; |
| }"#, |
| r#" |
| fn foo() { |
| static $0VAR_NAME: i32 = 1; |
| VAR_NAME + 1; |
| }"#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_last_expr() { |
| cov_mark::check!(test_extract_var_last_expr); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| bar($01 + 1$0) |
| } |
| "#, |
| r#" |
| fn foo() { |
| let $0var_name = 1 + 1; |
| bar(var_name) |
| } |
| "#, |
| "Extract into variable", |
| ); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() -> i32 { |
| $0bar(1 + 1)$0 |
| } |
| |
| fn bar(i: i32) -> i32 { |
| i |
| } |
| "#, |
| r#" |
| fn foo() -> i32 { |
| let $0bar = bar(1 + 1); |
| bar |
| } |
| |
| fn bar(i: i32) -> i32 { |
| i |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_const_last_expr() { |
| cov_mark::check!(test_extract_var_last_expr); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| bar($01 + 1$0) |
| } |
| "#, |
| r#" |
| fn foo() { |
| const $0VAR_NAME: i32 = 1 + 1; |
| bar(VAR_NAME) |
| } |
| "#, |
| "Extract into constant", |
| ); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() -> i32 { |
| $0bar(1 + 1)$0 |
| } |
| |
| const fn bar(i: i32) -> i32 { |
| i |
| } |
| "#, |
| r#" |
| fn foo() -> i32 { |
| const $0BAR: i32 = bar(1 + 1); |
| BAR |
| } |
| |
| const fn bar(i: i32) -> i32 { |
| i |
| } |
| "#, |
| "Extract into constant", |
| ) |
| } |
| |
| #[test] |
| fn extract_static_last_expr() { |
| cov_mark::check!(test_extract_var_last_expr); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| bar($01 + 1$0) |
| } |
| "#, |
| r#" |
| fn foo() { |
| static $0VAR_NAME: i32 = 1 + 1; |
| bar(VAR_NAME) |
| } |
| "#, |
| "Extract into static", |
| ); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() -> i32 { |
| $0bar(1 + 1)$0 |
| } |
| |
| const fn bar(i: i32) -> i32 { |
| i |
| } |
| "#, |
| r#" |
| fn foo() -> i32 { |
| static $0BAR: i32 = bar(1 + 1); |
| BAR |
| } |
| |
| const fn bar(i: i32) -> i32 { |
| i |
| } |
| "#, |
| "Extract into static", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_in_match_arm_no_block() { |
| cov_mark::check!(test_extract_var_in_match_arm_no_block); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let x = true; |
| let tuple = match x { |
| true => ($02 + 2$0, true) |
| _ => (0, false) |
| }; |
| } |
| "#, |
| r#" |
| fn main() { |
| let x = true; |
| let tuple = match x { |
| true => { |
| let $0var_name = 2 + 2; |
| (var_name, true) |
| } |
| _ => (0, false) |
| }; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_in_match_arm_with_block() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let x = true; |
| let tuple = match x { |
| true => { |
| let y = 1; |
| ($02 + y$0, true) |
| } |
| _ => (0, false) |
| }; |
| } |
| "#, |
| r#" |
| fn main() { |
| let x = true; |
| let tuple = match x { |
| true => { |
| let y = 1; |
| let $0var_name = 2 + y; |
| (var_name, true) |
| } |
| _ => (0, false) |
| }; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_in_match_guard() { |
| cov_mark::check!(test_extract_var_in_match_guard); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| match () { |
| () if $010 > 0$0 => 1 |
| _ => 2 |
| }; |
| } |
| "#, |
| r#" |
| fn main() { |
| let $0var_name = 10 > 0; |
| match () { |
| () if var_name => 1 |
| _ => 2 |
| }; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_in_closure_no_block() { |
| cov_mark::check!(test_extract_var_in_closure_no_block); |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let lambda = |x: u32| $0x * 2$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| let lambda = |x: u32| { |
| let $0var_name = x * 2; |
| var_name |
| }; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_in_closure_with_block() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let lambda = |x: u32| { $0x * 2$0 }; |
| } |
| "#, |
| r#" |
| fn main() { |
| let lambda = |x: u32| { let $0var_name = x * 2; var_name }; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_path_simple() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let o = $0Some(true)$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| let $0var_name = Some(true); |
| let o = var_name; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_path_method() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let v = $0bar.foo()$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| let $0foo = bar.foo(); |
| let v = foo; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_return() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() -> u32 { |
| $0return 2 + 2$0; |
| } |
| "#, |
| r#" |
| fn foo() -> u32 { |
| let $0var_name = 2 + 2; |
| return var_name; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_does_not_add_extra_whitespace() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() -> u32 { |
| |
| |
| $0return 2 + 2$0; |
| } |
| "#, |
| r#" |
| fn foo() -> u32 { |
| |
| |
| let $0var_name = 2 + 2; |
| return var_name; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() -> u32 { |
| |
| $0return 2 + 2$0; |
| } |
| "#, |
| r#" |
| fn foo() -> u32 { |
| |
| let $0var_name = 2 + 2; |
| return var_name; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() -> u32 { |
| let foo = 1; |
| |
| // bar |
| |
| |
| $0return 2 + 2$0; |
| } |
| "#, |
| r#" |
| fn foo() -> u32 { |
| let foo = 1; |
| |
| // bar |
| |
| |
| let $0var_name = 2 + 2; |
| return var_name; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_break() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let result = loop { |
| $0break 2 + 2$0; |
| }; |
| } |
| "#, |
| r#" |
| fn main() { |
| let result = loop { |
| let $0var_name = 2 + 2; |
| break var_name; |
| }; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_for_cast() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn main() { |
| let v = $00f32 as u32$0; |
| } |
| "#, |
| r#" |
| fn main() { |
| let $0var_name = 0f32 as u32; |
| let v = var_name; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_field_shorthand() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct S { |
| foo: i32 |
| } |
| |
| fn main() { |
| S { foo: $01 + 1$0 } |
| } |
| "#, |
| r#" |
| struct S { |
| foo: i32 |
| } |
| |
| fn main() { |
| let $0foo = 1 + 1; |
| S { foo } |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_name_from_type() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Test(i32); |
| |
| fn foo() -> Test { |
| $0{ Test(10) }$0 |
| } |
| "#, |
| r#" |
| struct Test(i32); |
| |
| fn foo() -> Test { |
| let $0test = { Test(10) }; |
| test |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_name_from_parameter() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn bar(test: u32, size: u32) |
| |
| fn foo() { |
| bar(1, $01+1$0); |
| } |
| "#, |
| r#" |
| fn bar(test: u32, size: u32) |
| |
| fn foo() { |
| let $0size = 1+1; |
| bar(1, size); |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_parameter_name_has_precedence_over_type() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct TextSize(u32); |
| fn bar(test: u32, size: TextSize) |
| |
| fn foo() { |
| bar(1, $0{ TextSize(1+1) }$0); |
| } |
| "#, |
| r#" |
| struct TextSize(u32); |
| fn bar(test: u32, size: TextSize) |
| |
| fn foo() { |
| let $0size = { TextSize(1+1) }; |
| bar(1, size); |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_name_from_function() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn is_required(test: u32, size: u32) -> bool |
| |
| fn foo() -> bool { |
| $0is_required(1, 2)$0 |
| } |
| "#, |
| r#" |
| fn is_required(test: u32, size: u32) -> bool |
| |
| fn foo() -> bool { |
| let $0is_required = is_required(1, 2); |
| is_required |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_name_from_method() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct S; |
| impl S { |
| fn bar(&self, n: u32) -> u32 { n } |
| } |
| |
| fn foo() -> u32 { |
| $0S.bar(1)$0 |
| } |
| "#, |
| r#" |
| struct S; |
| impl S { |
| fn bar(&self, n: u32) -> u32 { n } |
| } |
| |
| fn foo() -> u32 { |
| let $0bar = S.bar(1); |
| bar |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_name_from_method_param() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct S; |
| impl S { |
| fn bar(&self, n: u32, size: u32) { n } |
| } |
| |
| fn foo() { |
| S.bar($01 + 1$0, 2) |
| } |
| "#, |
| r#" |
| struct S; |
| impl S { |
| fn bar(&self, n: u32, size: u32) { n } |
| } |
| |
| fn foo() { |
| let $0n = 1 + 1; |
| S.bar(n, 2) |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_name_from_ufcs_method_param() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct S; |
| impl S { |
| fn bar(&self, n: u32, size: u32) { n } |
| } |
| |
| fn foo() { |
| S::bar(&S, $01 + 1$0, 2) |
| } |
| "#, |
| r#" |
| struct S; |
| impl S { |
| fn bar(&self, n: u32, size: u32) { n } |
| } |
| |
| fn foo() { |
| let $0n = 1 + 1; |
| S::bar(&S, n, 2) |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_var_parameter_name_has_precedence_over_function() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn bar(test: u32, size: u32) |
| |
| fn foo() { |
| bar(1, $0symbol_size(1, 2)$0); |
| } |
| "#, |
| r#" |
| fn bar(test: u32, size: u32) |
| |
| fn foo() { |
| let $0size = symbol_size(1, 2); |
| bar(1, size); |
| } |
| "#, |
| "Extract into variable", |
| ) |
| } |
| |
| #[test] |
| fn extract_macro_call() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Vec; |
| macro_rules! vec { |
| () => {Vec} |
| } |
| fn main() { |
| let _ = $0vec![]$0; |
| } |
| "#, |
| r#" |
| struct Vec; |
| macro_rules! vec { |
| () => {Vec} |
| } |
| fn main() { |
| let $0items = vec![]; |
| let _ = items; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Vec; |
| macro_rules! vec { |
| () => {Vec} |
| } |
| fn main() { |
| let _ = $0vec![]$0; |
| } |
| "#, |
| r#" |
| struct Vec; |
| macro_rules! vec { |
| () => {Vec} |
| } |
| fn main() { |
| const $0ITEMS: Vec = vec![]; |
| let _ = ITEMS; |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Vec; |
| macro_rules! vec { |
| () => {Vec} |
| } |
| fn main() { |
| let _ = $0vec![]$0; |
| } |
| "#, |
| r#" |
| struct Vec; |
| macro_rules! vec { |
| () => {Vec} |
| } |
| fn main() { |
| static $0ITEMS: Vec = vec![]; |
| let _ = ITEMS; |
| } |
| "#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_for_return_not_applicable() { |
| check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); |
| } |
| |
| #[test] |
| fn extract_var_for_break_not_applicable() { |
| check_assist_not_applicable(extract_variable, "fn main() { loop { $0break$0; }; }"); |
| } |
| |
| #[test] |
| fn extract_var_unit_expr_not_applicable() { |
| check_assist_not_applicable( |
| extract_variable, |
| r#" |
| fn foo() { |
| let mut i = 3; |
| $0if i >= 0 { |
| i += 1; |
| } else { |
| i -= 1; |
| }$0 |
| }"#, |
| ); |
| } |
| |
| // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic |
| #[test] |
| fn extract_var_target() { |
| check_assist_target(extract_variable, r#"fn foo() -> u32 { $0return 2 + 2$0; }"#, "2 + 2"); |
| |
| check_assist_target( |
| extract_variable, |
| r#" |
| fn main() { |
| let x = true; |
| let tuple = match x { |
| true => ($02 + 2$0, true) |
| _ => (0, false) |
| }; |
| } |
| "#, |
| "2 + 2", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_no_block_body() { |
| check_assist_not_applicable_by_label( |
| extract_variable, |
| r#" |
| const X: usize = $0100$0; |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_const_no_block_body() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| const FOO: i32 = foo($0100$0); |
| "#, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| const $0X: i32 = 100; |
| const FOO: i32 = foo(X); |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| mod foo { |
| enum Foo { |
| Bar, |
| Baz = $042$0, |
| } |
| } |
| "#, |
| r#" |
| mod foo { |
| const $0VAR_NAME: isize = 42; |
| enum Foo { |
| Bar, |
| Baz = VAR_NAME, |
| } |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| trait Hello { |
| const World: i32; |
| } |
| |
| struct Bar; |
| impl Hello for Bar { |
| const World = foo($042$0); |
| } |
| "#, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| trait Hello { |
| const World: i32; |
| } |
| |
| struct Bar; |
| impl Hello for Bar { |
| const $0X: i32 = 42; |
| const World = foo(X); |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| fn bar() { |
| const BAZ: i32 = foo($042$0); |
| } |
| "#, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| fn bar() { |
| const $0X: i32 = 42; |
| const BAZ: i32 = foo(X); |
| } |
| "#, |
| "Extract into constant", |
| ); |
| } |
| |
| #[test] |
| fn extract_static_no_block_body() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| const FOO: i32 = foo($0100$0); |
| "#, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| static $0X: i32 = 100; |
| const FOO: i32 = foo(X); |
| "#, |
| "Extract into static", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| mod foo { |
| enum Foo { |
| Bar, |
| Baz = $042$0, |
| } |
| } |
| "#, |
| r#" |
| mod foo { |
| static $0VAR_NAME: isize = 42; |
| enum Foo { |
| Bar, |
| Baz = VAR_NAME, |
| } |
| } |
| "#, |
| "Extract into static", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| trait Hello { |
| const World: i32; |
| } |
| |
| struct Bar; |
| impl Hello for Bar { |
| const World = foo($042$0); |
| } |
| "#, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| trait Hello { |
| const World: i32; |
| } |
| |
| struct Bar; |
| impl Hello for Bar { |
| static $0X: i32 = 42; |
| const World = foo(X); |
| } |
| "#, |
| "Extract into static", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| fn bar() { |
| const BAZ: i32 = foo($042$0); |
| } |
| "#, |
| r#" |
| const fn foo(x: i32) -> i32 { |
| x |
| } |
| |
| fn bar() { |
| static $0X: i32 = 42; |
| const BAZ: i32 = foo(X); |
| } |
| "#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_mutable_reference_parameter() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct S { |
| vec: Vec<u8> |
| } |
| |
| struct Vec<T>; |
| impl<T> Vec<T> { |
| fn push(&mut self, _:usize) {} |
| } |
| |
| fn foo(s: &mut S) { |
| $0s.vec$0.push(0); |
| }"#, |
| r#" |
| struct S { |
| vec: Vec<u8> |
| } |
| |
| struct Vec<T>; |
| impl<T> Vec<T> { |
| fn push(&mut self, _:usize) {} |
| } |
| |
| fn foo(s: &mut S) { |
| let $0items = &mut s.vec; |
| items.push(0); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn dont_extract_const_mutable_reference_parameter() { |
| check_assist_not_applicable_by_label( |
| extract_variable, |
| r#" |
| struct S { |
| vec: Vec<u8> |
| } |
| |
| struct Vec<T>; |
| impl<T> Vec<T> { |
| fn push(&mut self, _:usize) {} |
| } |
| |
| fn foo(s: &mut S) { |
| $0s.vec$0.push(0); |
| }"#, |
| "Extract into constant", |
| ); |
| } |
| |
| #[test] |
| fn dont_extract_static_mutable_reference_parameter() { |
| check_assist_not_applicable_by_label( |
| extract_variable, |
| r#" |
| struct S { |
| vec: Vec<u8> |
| } |
| |
| struct Vec<T>; |
| impl<T> Vec<T> { |
| fn push(&mut self, _:usize) {} |
| } |
| |
| fn foo(s: &mut S) { |
| $0s.vec$0.push(0); |
| }"#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_mutable_reference_parameter_deep_nesting() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Y { |
| field: X |
| } |
| struct X { |
| field: S |
| } |
| struct S { |
| vec: Vec<u8> |
| } |
| struct Vec<T>; |
| impl<T> Vec<T> { |
| fn push(&mut self, _:usize) {} |
| } |
| |
| fn foo(f: &mut Y) { |
| $0f.field.field.vec$0.push(0); |
| }"#, |
| r#" |
| struct Y { |
| field: X |
| } |
| struct X { |
| field: S |
| } |
| struct S { |
| vec: Vec<u8> |
| } |
| struct Vec<T>; |
| impl<T> Vec<T> { |
| fn push(&mut self, _:usize) {} |
| } |
| |
| fn foo(f: &mut Y) { |
| let $0items = &mut f.field.field.vec; |
| items.push(0); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_reference_parameter() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct X; |
| |
| impl X { |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| struct S { |
| sub: X |
| } |
| |
| fn foo(s: &S) { |
| $0s.sub$0.do_thing(); |
| }"#, |
| r#" |
| struct X; |
| |
| impl X { |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| struct S { |
| sub: X |
| } |
| |
| fn foo(s: &S) { |
| let $0x = &s.sub; |
| x.do_thing(); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_index_deref() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| //- minicore: index |
| struct X; |
| |
| impl std::ops::Index<usize> for X { |
| type Output = i32; |
| fn index(&self) -> &Self::Output { 0 } |
| } |
| |
| struct S { |
| sub: X |
| } |
| |
| fn foo(s: &S) { |
| $0s.sub$0[0]; |
| }"#, |
| r#" |
| struct X; |
| |
| impl std::ops::Index<usize> for X { |
| type Output = i32; |
| fn index(&self) -> &Self::Output { 0 } |
| } |
| |
| struct S { |
| sub: X |
| } |
| |
| fn foo(s: &S) { |
| let $0sub = &s.sub; |
| sub[0]; |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_reference_parameter_deep_nesting() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Z; |
| impl Z { |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| struct Y { |
| field: Z |
| } |
| |
| struct X { |
| field: Y |
| } |
| |
| struct S { |
| sub: X |
| } |
| |
| fn foo(s: &S) { |
| $0s.sub.field.field$0.do_thing(); |
| }"#, |
| r#" |
| struct Z; |
| impl Z { |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| struct Y { |
| field: Z |
| } |
| |
| struct X { |
| field: Y |
| } |
| |
| struct S { |
| sub: X |
| } |
| |
| fn foo(s: &S) { |
| let $0z = &s.sub.field.field; |
| z.do_thing(); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_regular_parameter() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct X; |
| |
| impl X { |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| struct S { |
| sub: X |
| } |
| |
| fn foo(s: S) { |
| $0s.sub$0.do_thing(); |
| }"#, |
| r#" |
| struct X; |
| |
| impl X { |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| struct S { |
| sub: X |
| } |
| |
| fn foo(s: S) { |
| let $0x = &s.sub; |
| x.do_thing(); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_mutable_reference_local() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct X; |
| |
| struct S { |
| sub: X |
| } |
| |
| impl S { |
| fn new() -> S { |
| S { |
| sub: X::new() |
| } |
| } |
| } |
| |
| impl X { |
| fn new() -> X { |
| X { } |
| } |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| |
| fn foo() { |
| let local = &mut S::new(); |
| $0local.sub$0.do_thing(); |
| }"#, |
| r#" |
| struct X; |
| |
| struct S { |
| sub: X |
| } |
| |
| impl S { |
| fn new() -> S { |
| S { |
| sub: X::new() |
| } |
| } |
| } |
| |
| impl X { |
| fn new() -> X { |
| X { } |
| } |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| |
| fn foo() { |
| let local = &mut S::new(); |
| let $0x = &local.sub; |
| x.do_thing(); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_reference_local() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct X; |
| |
| struct S { |
| sub: X |
| } |
| |
| impl S { |
| fn new() -> S { |
| S { |
| sub: X::new() |
| } |
| } |
| } |
| |
| impl X { |
| fn new() -> X { |
| X { } |
| } |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| |
| fn foo() { |
| let local = &S::new(); |
| $0local.sub$0.do_thing(); |
| }"#, |
| r#" |
| struct X; |
| |
| struct S { |
| sub: X |
| } |
| |
| impl S { |
| fn new() -> S { |
| S { |
| sub: X::new() |
| } |
| } |
| } |
| |
| impl X { |
| fn new() -> X { |
| X { } |
| } |
| fn do_thing(&self) { |
| |
| } |
| } |
| |
| |
| fn foo() { |
| let local = &S::new(); |
| let $0x = &local.sub; |
| x.do_thing(); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_var_for_mutable_borrow() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| let v = &mut $00$0; |
| }"#, |
| r#" |
| fn foo() { |
| let mut $0var_name = 0; |
| let v = &mut var_name; |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn dont_extract_const_for_mutable_borrow() { |
| check_assist_not_applicable_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| let v = &mut $00$0; |
| }"#, |
| "Extract into constant", |
| ); |
| } |
| |
| #[test] |
| fn dont_extract_static_for_mutable_borrow() { |
| check_assist_not_applicable_by_label( |
| extract_variable, |
| r#" |
| fn foo() { |
| let v = &mut $00$0; |
| }"#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn generates_no_ref_on_calls() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct S; |
| impl S { |
| fn do_work(&mut self) {} |
| } |
| fn bar() -> S { S } |
| fn foo() { |
| $0bar()$0.do_work(); |
| }"#, |
| r#" |
| struct S; |
| impl S { |
| fn do_work(&mut self) {} |
| } |
| fn bar() -> S { S } |
| fn foo() { |
| let mut $0bar = bar(); |
| bar.do_work(); |
| }"#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn generates_no_ref_for_deref() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct S; |
| impl S { |
| fn do_work(&mut self) {} |
| } |
| fn bar() -> S { S } |
| fn foo() { |
| let v = &mut &mut bar(); |
| $0(**v)$0.do_work(); |
| } |
| "#, |
| r#" |
| struct S; |
| impl S { |
| fn do_work(&mut self) {} |
| } |
| fn bar() -> S { S } |
| fn foo() { |
| let v = &mut &mut bar(); |
| let $0s = *v; |
| s.do_work(); |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| |
| #[test] |
| fn extract_string_literal() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Entry<'a>(&'a str); |
| fn foo() { |
| let entry = Entry($0"Hello"$0); |
| } |
| "#, |
| r#" |
| struct Entry<'a>(&'a str); |
| fn foo() { |
| let $0hello = "Hello"; |
| let entry = Entry(hello); |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Entry<'a>(&'a str); |
| fn foo() { |
| let entry = Entry($0"Hello"$0); |
| } |
| "#, |
| r#" |
| struct Entry<'a>(&'a str); |
| fn foo() { |
| const $0HELLO: &str = "Hello"; |
| let entry = Entry(HELLO); |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Entry<'a>(&'a str); |
| fn foo() { |
| let entry = Entry($0"Hello"$0); |
| } |
| "#, |
| r#" |
| struct Entry<'a>(&'a str); |
| fn foo() { |
| static $0HELLO: &str = "Hello"; |
| let entry = Entry(HELLO); |
| } |
| "#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn extract_variable_string_literal_use_field_shorthand() { |
| // When field shorthand is available, it should |
| // only be used when extracting into a variable |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Entry<'a> { message: &'a str } |
| fn foo() { |
| let entry = Entry { message: $0"Hello"$0 }; |
| } |
| "#, |
| r#" |
| struct Entry<'a> { message: &'a str } |
| fn foo() { |
| let $0message = "Hello"; |
| let entry = Entry { message }; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Entry<'a> { message: &'a str } |
| fn foo() { |
| let entry = Entry { message: $0"Hello"$0 }; |
| } |
| "#, |
| r#" |
| struct Entry<'a> { message: &'a str } |
| fn foo() { |
| const $0HELLO: &str = "Hello"; |
| let entry = Entry { message: HELLO }; |
| } |
| "#, |
| "Extract into constant", |
| ); |
| |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct Entry<'a> { message: &'a str } |
| fn foo() { |
| let entry = Entry { message: $0"Hello"$0 }; |
| } |
| "#, |
| r#" |
| struct Entry<'a> { message: &'a str } |
| fn foo() { |
| static $0HELLO: &str = "Hello"; |
| let entry = Entry { message: HELLO }; |
| } |
| "#, |
| "Extract into static", |
| ); |
| } |
| |
| #[test] |
| fn extract_variable_name_conflicts() { |
| check_assist_by_label( |
| extract_variable, |
| r#" |
| struct S { x: i32 }; |
| |
| fn main() { |
| let s = 2; |
| let t = $0S { x: 1 }$0; |
| let t2 = t; |
| let x = s; |
| } |
| "#, |
| r#" |
| struct S { x: i32 }; |
| |
| fn main() { |
| let s = 2; |
| let $0s1 = S { x: 1 }; |
| let t = s1; |
| let t2 = t; |
| let x = s; |
| } |
| "#, |
| "Extract into variable", |
| ); |
| } |
| } |