From 0cad614b3b202ded9a48d7ed07f36284ec393caf Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:04:54 -0500 Subject: [PATCH] feat: Add an assist to extract an expression into a static --- .../src/handlers/extract_variable.rs | 588 +++++++++++++++--- crates/ide-assists/src/tests.rs | 192 +++++- crates/ide-assists/src/tests/generated.rs | 18 + 3 files changed, 682 insertions(+), 116 deletions(-) diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index 6670eac6a7..a8d71ed7f4 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -1,14 +1,12 @@ use hir::{HirDisplay, TypeInfo}; -use ide_db::syntax_helpers::suggest_name; +use ide_db::{assists::GroupLabel, syntax_helpers::suggest_name}; use syntax::{ ast::{ self, edit::IndentLevel, edit_in_place::Indent, make, syntax_factory::SyntaxFactory, AstNode, }, syntax_editor::Position, - NodeOrToken, - SyntaxKind::{self}, - SyntaxNode, T, + NodeOrToken, SyntaxKind, SyntaxNode, T, }; use crate::{utils::is_body_const, AssistContext, AssistId, AssistKind, Assists}; @@ -46,6 +44,23 @@ use crate::{utils::is_body_const, AssistContext, AssistId, AssistKind, Assists}; // 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![;]) { @@ -114,15 +129,20 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op let Some(anchor) = Anchor::from(&to_extract, kind) else { continue; }; + let ty_string = match kind { - ExtractionKind::Constant => { + 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() { + if needs_mut + || !is_body_const(&ctx.sema, &to_extract_no_ref) + || ty.is_unknown() + || ty.is_mutable_reference() + { continue; } @@ -135,92 +155,111 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op _ => "".to_owned(), }; - acc.add(kind.assist_id(), kind.label(), target, |edit| { - let (var_name, expr_replace) = kind.get_name_and_expr(ctx, &to_extract); + 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::new(); - let mut editor = edit.make_editor(&expr_replace); + let make = SyntaxFactory::new(); + 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)); + 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) + if let Some(cap) = ctx.config.snippet_cap { + let tabstop = edit.make_tabstop_before(cap); + editor.add_annotation(pat_name.syntax().clone(), tabstop); } - 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, pat_name, ast_ty, initializer)).into() - } - }; + 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(), + }; - 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); + 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, 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() + } + }; - // 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() - }; + 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); - editor.insert_all( - Position::before(place), - vec![ - new_stmt.syntax().clone().into(), - make::tokens::whitespace(&trailing_ws).into(), - ], - ); + // 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.replace(expr_replace, name_expr.syntax()); - } - Anchor::Replace(stmt) => { - cov_mark::hit!(test_extract_var_expr_stmt); + editor.insert_all( + Position::before(place), + vec![ + new_stmt.syntax().clone().into(), + make::tokens::whitespace(&trailing_ws).into(), + ], + ); - 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())) - }; + } + Anchor::Replace(stmt) => { + cov_mark::hit!(test_extract_var_expr_stmt); - editor.replace(to_wrap.syntax(), block.syntax()); + editor.replace(stmt.syntax(), new_stmt.syntax()); + } + Anchor::WrapInBlock(to_wrap) => { + let indent_to = to_wrap.indent_level(); - // fixup indentation of block - block.indent(indent_to); + 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())) + }; + + editor.replace(to_wrap.syntax(), block.syntax()); + + // fixup indentation of block + block.indent(indent_to); + } } - } - editor.add_mappings(make.finish_with_mappings()); - edit.add_file_edits(ctx.file_id(), editor); - edit.rename(); - }); + editor.add_mappings(make.finish_with_mappings()); + edit.add_file_edits(ctx.file_id(), editor); + edit.rename(); + }, + ); } Some(()) @@ -251,15 +290,18 @@ fn valid_target_expr(node: SyntaxNode) -> Option { enum ExtractionKind { Variable, Constant, + Static, } impl ExtractionKind { - const ALL: &'static [ExtractionKind] = &[ExtractionKind::Variable, ExtractionKind::Constant]; + 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(s, AssistKind::RefactorExtract) @@ -269,6 +311,7 @@ impl ExtractionKind { match self { ExtractionKind::Variable => "Extract into variable", ExtractionKind::Constant => "Extract into constant", + ExtractionKind::Static => "Extract into static", } } @@ -291,7 +334,7 @@ impl ExtractionKind { let var_name = match self { ExtractionKind::Variable => var_name, - ExtractionKind::Constant => var_name.to_uppercase(), + ExtractionKind::Constant | ExtractionKind::Static => var_name.to_uppercase(), }; (var_name, expr_replace) @@ -351,7 +394,7 @@ impl Anchor { }); match kind { - ExtractionKind::Constant if result.is_none() => { + 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()?; @@ -381,21 +424,6 @@ mod tests { use super::*; - #[test] - fn now_bad() { - // unknown type - check_assist_not_applicable_by_label( - extract_variable, - r#" -fn main() { - let a = Some(2); - a.is_some();$0 -} -"#, - "Extract into constant", - ); - } - #[test] fn extract_var_simple_without_select() { check_assist_by_label( @@ -604,7 +632,102 @@ fn main() { } #[test] - fn extract_var_unit_expr_without_select_not_applicable() { + fn extract_static_simple_without_select() { + check_assist_by_label( + extract_variable, + r#" +fn main() -> i32 { + if true { + 1 + } else { + 2 + }$0 +} +"#, + 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 $0VAR_NAME: &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#" @@ -664,7 +787,24 @@ fn foo() { } #[test] - fn extract_var_in_comment_is_not_applicable() { + 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; }"#); } @@ -732,6 +872,38 @@ fn foo() { ); } + #[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( @@ -766,6 +938,23 @@ fn foo() { ); } + #[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); @@ -852,6 +1041,49 @@ const fn bar(i: i32) -> i32 { ) } + #[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); @@ -1427,6 +1659,30 @@ fn main() { "#, "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 $0VEC: Vec = vec![]; + let _ = VEC; +} +"#, + "Extract into static", + ); } #[test] @@ -1589,6 +1845,109 @@ fn bar() { ); } + #[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( @@ -1641,7 +2000,28 @@ impl Vec { fn foo(s: &mut S) { $0s.vec$0.push(0); }"#, - "Extract into const", + "Extract into constant", + ); + } + + #[test] + fn dont_extract_static_mutable_reference_parameter() { + check_assist_not_applicable_by_label( + extract_variable, + r#" +struct S { + vec: Vec +} + +struct Vec; +impl Vec { + fn push(&mut self, _:usize) {} +} + +fn foo(s: &mut S) { + $0s.vec$0.push(0); +}"#, + "Extract into static", ); } @@ -2030,6 +2410,18 @@ fn foo() { ); } + #[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( diff --git a/crates/ide-assists/src/tests.rs b/crates/ide-assists/src/tests.rs index 4f7f03764f..e517dd4682 100644 --- a/crates/ide-assists/src/tests.rs +++ b/crates/ide-assists/src/tests.rs @@ -362,8 +362,7 @@ pub fn test_some_range(a: int) -> bool { expect![[r#" Convert integer base - Extract into variable - Extract into constant + Extract into... Extract into function Replace if let with match "#]] @@ -392,8 +391,7 @@ pub fn test_some_range(a: int) -> bool { expect![[r#" Convert integer base - Extract into variable - Extract into constant + Extract into... Extract into function Replace if let with match "#]] @@ -407,8 +405,7 @@ pub fn test_some_range(a: int) -> bool { let expected = labels(&assists); expect![[r#" - Extract into variable - Extract into constant + Extract into... Extract into function "#]] .assert_eq(&expected); @@ -443,7 +440,7 @@ pub fn test_some_range(a: int) -> bool { { let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange.into()); - assert_eq!(3, assists.len()); + assert_eq!(4, assists.len()); let mut assists = assists.into_iter(); let extract_into_variable_assist = assists.next().unwrap(); @@ -454,7 +451,11 @@ pub fn test_some_range(a: int) -> bool { RefactorExtract, ), label: "Extract into variable", - group: None, + group: Some( + GroupLabel( + "Extract into...", + ), + ), target: 59..60, source_change: None, command: None, @@ -470,7 +471,11 @@ pub fn test_some_range(a: int) -> bool { RefactorExtract, ), label: "Extract into constant", - group: None, + group: Some( + GroupLabel( + "Extract into...", + ), + ), target: 59..60, source_change: None, command: None, @@ -478,6 +483,26 @@ pub fn test_some_range(a: int) -> bool { "#]] .assert_debug_eq(&extract_into_constant_assist); + let extract_into_static_assist = assists.next().unwrap(); + expect![[r#" + Assist { + id: AssistId( + "extract_static", + RefactorExtract, + ), + label: "Extract into static", + group: Some( + GroupLabel( + "Extract into...", + ), + ), + target: 59..60, + source_change: None, + command: None, + } + "#]] + .assert_debug_eq(&extract_into_static_assist); + let extract_into_function_assist = assists.next().unwrap(); expect![[r#" Assist { @@ -505,7 +530,7 @@ pub fn test_some_range(a: int) -> bool { }), frange.into(), ); - assert_eq!(3, assists.len()); + assert_eq!(4, assists.len()); let mut assists = assists.into_iter(); let extract_into_variable_assist = assists.next().unwrap(); @@ -516,7 +541,11 @@ pub fn test_some_range(a: int) -> bool { RefactorExtract, ), label: "Extract into variable", - group: None, + group: Some( + GroupLabel( + "Extract into...", + ), + ), target: 59..60, source_change: None, command: None, @@ -532,7 +561,11 @@ pub fn test_some_range(a: int) -> bool { RefactorExtract, ), label: "Extract into constant", - group: None, + group: Some( + GroupLabel( + "Extract into...", + ), + ), target: 59..60, source_change: None, command: None, @@ -540,6 +573,26 @@ pub fn test_some_range(a: int) -> bool { "#]] .assert_debug_eq(&extract_into_constant_assist); + let extract_into_static_assist = assists.next().unwrap(); + expect![[r#" + Assist { + id: AssistId( + "extract_static", + RefactorExtract, + ), + label: "Extract into static", + group: Some( + GroupLabel( + "Extract into...", + ), + ), + target: 59..60, + source_change: None, + command: None, + } + "#]] + .assert_debug_eq(&extract_into_static_assist); + let extract_into_function_assist = assists.next().unwrap(); expect![[r#" Assist { @@ -567,7 +620,7 @@ pub fn test_some_range(a: int) -> bool { }), frange.into(), ); - assert_eq!(3, assists.len()); + assert_eq!(4, assists.len()); let mut assists = assists.into_iter(); let extract_into_variable_assist = assists.next().unwrap(); @@ -578,7 +631,11 @@ pub fn test_some_range(a: int) -> bool { RefactorExtract, ), label: "Extract into variable", - group: None, + group: Some( + GroupLabel( + "Extract into...", + ), + ), target: 59..60, source_change: Some( SourceChange { @@ -637,7 +694,11 @@ pub fn test_some_range(a: int) -> bool { RefactorExtract, ), label: "Extract into constant", - group: None, + group: Some( + GroupLabel( + "Extract into...", + ), + ), target: 59..60, source_change: None, command: None, @@ -645,6 +706,26 @@ pub fn test_some_range(a: int) -> bool { "#]] .assert_debug_eq(&extract_into_constant_assist); + let extract_into_static_assist = assists.next().unwrap(); + expect![[r#" + Assist { + id: AssistId( + "extract_static", + RefactorExtract, + ), + label: "Extract into static", + group: Some( + GroupLabel( + "Extract into...", + ), + ), + target: 59..60, + source_change: None, + command: None, + } + "#]] + .assert_debug_eq(&extract_into_static_assist); + let extract_into_function_assist = assists.next().unwrap(); expect![[r#" Assist { @@ -664,7 +745,7 @@ pub fn test_some_range(a: int) -> bool { { let assists = assists(&db, &cfg, AssistResolveStrategy::All, frange.into()); - assert_eq!(3, assists.len()); + assert_eq!(4, assists.len()); let mut assists = assists.into_iter(); let extract_into_variable_assist = assists.next().unwrap(); @@ -675,7 +756,11 @@ pub fn test_some_range(a: int) -> bool { RefactorExtract, ), label: "Extract into variable", - group: None, + group: Some( + GroupLabel( + "Extract into...", + ), + ), target: 59..60, source_change: Some( SourceChange { @@ -734,7 +819,11 @@ pub fn test_some_range(a: int) -> bool { RefactorExtract, ), label: "Extract into constant", - group: None, + group: Some( + GroupLabel( + "Extract into...", + ), + ), target: 59..60, source_change: Some( SourceChange { @@ -789,6 +878,73 @@ pub fn test_some_range(a: int) -> bool { "#]] .assert_debug_eq(&extract_into_constant_assist); + let extract_into_static_assist = assists.next().unwrap(); + expect![[r#" + Assist { + id: AssistId( + "extract_static", + RefactorExtract, + ), + label: "Extract into static", + group: Some( + GroupLabel( + "Extract into...", + ), + ), + target: 59..60, + source_change: Some( + SourceChange { + source_file_edits: { + FileId( + 0, + ): ( + TextEdit { + indels: [ + Indel { + insert: "static", + delete: 45..47, + }, + Indel { + insert: "VAR_NAME:", + delete: 48..60, + }, + Indel { + insert: "i32", + delete: 61..81, + }, + Indel { + insert: "=", + delete: 82..86, + }, + Indel { + insert: "5;\n if let 2..6 = VAR_NAME {\n true\n } else {\n false\n }", + delete: 87..108, + }, + ], + }, + Some( + SnippetEdit( + [ + ( + 0, + 52..52, + ), + ], + ), + ), + ), + }, + file_system_edits: [], + is_snippet: true, + }, + ), + command: Some( + Rename, + ), + } + "#]] + .assert_debug_eq(&extract_into_static_assist); + let extract_into_function_assist = assists.next().unwrap(); expect![[r#" Assist { diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index c1799b48ed..87c3d166ee 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -1024,6 +1024,24 @@ fn bar(name: i32) -> i32 { ) } +#[test] +fn doctest_extract_static() { + check_doc_test( + "extract_static", + r#####" +fn main() { + $0(1 + 2)$0 * 4; +} +"#####, + r#####" +fn main() { + static $0VAR_NAME: i32 = 1 + 2; + VAR_NAME * 4; +} +"#####, + ) +} + #[test] fn doctest_extract_struct_from_enum_variant() { check_doc_test(