feat: Add an assist to extract an expression into a static

This commit is contained in:
Giga Bowser 2024-12-11 14:04:54 -05:00
parent 135e71fcb3
commit 0cad614b3b
3 changed files with 682 additions and 116 deletions

View file

@ -1,14 +1,12 @@
use hir::{HirDisplay, TypeInfo}; use hir::{HirDisplay, TypeInfo};
use ide_db::syntax_helpers::suggest_name; use ide_db::{assists::GroupLabel, syntax_helpers::suggest_name};
use syntax::{ use syntax::{
ast::{ ast::{
self, edit::IndentLevel, edit_in_place::Indent, make, syntax_factory::SyntaxFactory, self, edit::IndentLevel, edit_in_place::Indent, make, syntax_factory::SyntaxFactory,
AstNode, AstNode,
}, },
syntax_editor::Position, syntax_editor::Position,
NodeOrToken, NodeOrToken, SyntaxKind, SyntaxNode, T,
SyntaxKind::{self},
SyntaxNode, T,
}; };
use crate::{utils::is_body_const, AssistContext, AssistId, AssistKind, Assists}; 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; // 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<()> { pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let node = if ctx.has_empty_selection() { let node = if ctx.has_empty_selection() {
if let Some(t) = ctx.token_at_offset().find(|it| it.kind() == T![;]) { 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 { let Some(anchor) = Anchor::from(&to_extract, kind) else {
continue; continue;
}; };
let ty_string = match kind { let ty_string = match kind {
ExtractionKind::Constant => { ExtractionKind::Constant | ExtractionKind::Static => {
let Some(ty) = ty.clone() else { let Some(ty) = ty.clone() else {
continue; continue;
}; };
// We can't mutably reference a const, nor can we define // We can't mutably reference a const, nor can we define
// one using a non-const expression or one of unknown type // 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; continue;
} }
@ -135,92 +155,111 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
_ => "".to_owned(), _ => "".to_owned(),
}; };
acc.add(kind.assist_id(), kind.label(), target, |edit| { acc.add_group(
let (var_name, expr_replace) = kind.get_name_and_expr(ctx, &to_extract); &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 make = SyntaxFactory::new();
let mut editor = edit.make_editor(&expr_replace); let mut editor = edit.make_editor(&expr_replace);
let pat_name = make.name(&var_name); let pat_name = make.name(&var_name);
let name_expr = make.expr_path(make::ext::ident_path(&var_name)); let name_expr = make.expr_path(make::ext::ident_path(&var_name));
if let Some(cap) = ctx.config.snippet_cap { if let Some(cap) = ctx.config.snippet_cap {
let tabstop = edit.make_tabstop_before(cap); let tabstop = edit.make_tabstop_before(cap);
editor.add_annotation(pat_name.syntax().clone(), tabstop); 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 { let initializer = match ty.as_ref().filter(|_| needs_ref) {
ExtractionKind::Variable => { Some(receiver_type) if receiver_type.is_mutable_reference() => {
let ident_pat = make.ident_pat(false, needs_mut, pat_name); make.expr_ref(to_extract_no_ref.clone(), true)
make.let_stmt(ident_pat.into(), None, Some(initializer)).into() }
} Some(receiver_type) if receiver_type.is_reference() => {
ExtractionKind::Constant => { make.expr_ref(to_extract_no_ref.clone(), false)
let ast_ty = make.ty(&ty_string); }
ast::Item::Const(make.item_const(None, pat_name, ast_ty, initializer)).into() _ => to_extract_no_ref.clone(),
} };
};
match &anchor { let new_stmt: ast::Stmt = match kind {
Anchor::Before(place) => { ExtractionKind::Variable => {
let prev_ws = place.prev_sibling_or_token().and_then(|it| it.into_token()); let ident_pat = make.ident_pat(false, needs_mut, pat_name);
let indent_to = IndentLevel::from_node(place); 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 match &anchor {
let trailing_ws = if prev_ws.is_some_and(|it| it.text().starts_with('\n')) { Anchor::Before(place) => {
format!("\n{indent_to}") let prev_ws = place.prev_sibling_or_token().and_then(|it| it.into_token());
} else { let indent_to = IndentLevel::from_node(place);
" ".to_owned()
};
editor.insert_all( // Adjust ws to insert depending on if this is all inline or on separate lines
Position::before(place), let trailing_ws = if prev_ws.is_some_and(|it| it.text().starts_with('\n')) {
vec![ format!("\n{indent_to}")
new_stmt.syntax().clone().into(), } else {
make::tokens::whitespace(&trailing_ws).into(), " ".to_owned()
], };
);
editor.replace(expr_replace, name_expr.syntax()); editor.insert_all(
} Position::before(place),
Anchor::Replace(stmt) => { vec![
cov_mark::hit!(test_extract_var_expr_stmt); 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()); 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 let block = if to_wrap.syntax() == &expr_replace {
block.indent(indent_to); // 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()); editor.add_mappings(make.finish_with_mappings());
edit.add_file_edits(ctx.file_id(), editor); edit.add_file_edits(ctx.file_id(), editor);
edit.rename(); edit.rename();
}); },
);
} }
Some(()) Some(())
@ -251,15 +290,18 @@ fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
enum ExtractionKind { enum ExtractionKind {
Variable, Variable,
Constant, Constant,
Static,
} }
impl ExtractionKind { 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 { fn assist_id(&self) -> AssistId {
let s = match self { let s = match self {
ExtractionKind::Variable => "extract_variable", ExtractionKind::Variable => "extract_variable",
ExtractionKind::Constant => "extract_constant", ExtractionKind::Constant => "extract_constant",
ExtractionKind::Static => "extract_static",
}; };
AssistId(s, AssistKind::RefactorExtract) AssistId(s, AssistKind::RefactorExtract)
@ -269,6 +311,7 @@ impl ExtractionKind {
match self { match self {
ExtractionKind::Variable => "Extract into variable", ExtractionKind::Variable => "Extract into variable",
ExtractionKind::Constant => "Extract into constant", ExtractionKind::Constant => "Extract into constant",
ExtractionKind::Static => "Extract into static",
} }
} }
@ -291,7 +334,7 @@ impl ExtractionKind {
let var_name = match self { let var_name = match self {
ExtractionKind::Variable => var_name, ExtractionKind::Variable => var_name,
ExtractionKind::Constant => var_name.to_uppercase(), ExtractionKind::Constant | ExtractionKind::Static => var_name.to_uppercase(),
}; };
(var_name, expr_replace) (var_name, expr_replace)
@ -351,7 +394,7 @@ impl Anchor {
}); });
match kind { match kind {
ExtractionKind::Constant if result.is_none() => { ExtractionKind::Constant | ExtractionKind::Static if result.is_none() => {
to_extract.syntax().ancestors().find_map(|node| { to_extract.syntax().ancestors().find_map(|node| {
let item = ast::Item::cast(node.clone())?; let item = ast::Item::cast(node.clone())?;
let parent = item.syntax().parent()?; let parent = item.syntax().parent()?;
@ -381,21 +424,6 @@ mod tests {
use super::*; 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] #[test]
fn extract_var_simple_without_select() { fn extract_var_simple_without_select() {
check_assist_by_label( check_assist_by_label(
@ -604,7 +632,102 @@ fn main() {
} }
#[test] #[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( check_assist_not_applicable(
extract_variable, extract_variable,
r#" r#"
@ -664,7 +787,24 @@ fn foo() {
} }
#[test] #[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); cov_mark::check!(extract_var_in_comment_is_not_applicable);
check_assist_not_applicable(extract_variable, r#"fn main() { 1 + /* $0comment$0 */ 1; }"#); 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] #[test]
fn extract_var_part_of_expr_stmt() { fn extract_var_part_of_expr_stmt() {
check_assist_by_label( 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] #[test]
fn extract_var_last_expr() { fn extract_var_last_expr() {
cov_mark::check!(test_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] #[test]
fn extract_var_in_match_arm_no_block() { fn extract_var_in_match_arm_no_block() {
cov_mark::check!(test_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", "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] #[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] #[test]
fn extract_var_mutable_reference_parameter() { fn extract_var_mutable_reference_parameter() {
check_assist_by_label( check_assist_by_label(
@ -1641,7 +2000,28 @@ impl<T> Vec<T> {
fn foo(s: &mut S) { fn foo(s: &mut S) {
$0s.vec$0.push(0); $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<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",
); );
} }
@ -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] #[test]
fn generates_no_ref_on_calls() { fn generates_no_ref_on_calls() {
check_assist_by_label( check_assist_by_label(

View file

@ -362,8 +362,7 @@ pub fn test_some_range(a: int) -> bool {
expect![[r#" expect![[r#"
Convert integer base Convert integer base
Extract into variable Extract into...
Extract into constant
Extract into function Extract into function
Replace if let with match Replace if let with match
"#]] "#]]
@ -392,8 +391,7 @@ pub fn test_some_range(a: int) -> bool {
expect![[r#" expect![[r#"
Convert integer base Convert integer base
Extract into variable Extract into...
Extract into constant
Extract into function Extract into function
Replace if let with match Replace if let with match
"#]] "#]]
@ -407,8 +405,7 @@ pub fn test_some_range(a: int) -> bool {
let expected = labels(&assists); let expected = labels(&assists);
expect![[r#" expect![[r#"
Extract into variable Extract into...
Extract into constant
Extract into function Extract into function
"#]] "#]]
.assert_eq(&expected); .assert_eq(&expected);
@ -443,7 +440,7 @@ pub fn test_some_range(a: int) -> bool {
{ {
let assists = assists(&db, &cfg, AssistResolveStrategy::None, frange.into()); 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 mut assists = assists.into_iter();
let extract_into_variable_assist = assists.next().unwrap(); let extract_into_variable_assist = assists.next().unwrap();
@ -454,7 +451,11 @@ pub fn test_some_range(a: int) -> bool {
RefactorExtract, RefactorExtract,
), ),
label: "Extract into variable", label: "Extract into variable",
group: None, group: Some(
GroupLabel(
"Extract into...",
),
),
target: 59..60, target: 59..60,
source_change: None, source_change: None,
command: None, command: None,
@ -470,7 +471,11 @@ pub fn test_some_range(a: int) -> bool {
RefactorExtract, RefactorExtract,
), ),
label: "Extract into constant", label: "Extract into constant",
group: None, group: Some(
GroupLabel(
"Extract into...",
),
),
target: 59..60, target: 59..60,
source_change: None, source_change: None,
command: None, command: None,
@ -478,6 +483,26 @@ pub fn test_some_range(a: int) -> bool {
"#]] "#]]
.assert_debug_eq(&extract_into_constant_assist); .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(); let extract_into_function_assist = assists.next().unwrap();
expect![[r#" expect![[r#"
Assist { Assist {
@ -505,7 +530,7 @@ pub fn test_some_range(a: int) -> bool {
}), }),
frange.into(), frange.into(),
); );
assert_eq!(3, assists.len()); assert_eq!(4, assists.len());
let mut assists = assists.into_iter(); let mut assists = assists.into_iter();
let extract_into_variable_assist = assists.next().unwrap(); let extract_into_variable_assist = assists.next().unwrap();
@ -516,7 +541,11 @@ pub fn test_some_range(a: int) -> bool {
RefactorExtract, RefactorExtract,
), ),
label: "Extract into variable", label: "Extract into variable",
group: None, group: Some(
GroupLabel(
"Extract into...",
),
),
target: 59..60, target: 59..60,
source_change: None, source_change: None,
command: None, command: None,
@ -532,7 +561,11 @@ pub fn test_some_range(a: int) -> bool {
RefactorExtract, RefactorExtract,
), ),
label: "Extract into constant", label: "Extract into constant",
group: None, group: Some(
GroupLabel(
"Extract into...",
),
),
target: 59..60, target: 59..60,
source_change: None, source_change: None,
command: None, command: None,
@ -540,6 +573,26 @@ pub fn test_some_range(a: int) -> bool {
"#]] "#]]
.assert_debug_eq(&extract_into_constant_assist); .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(); let extract_into_function_assist = assists.next().unwrap();
expect![[r#" expect![[r#"
Assist { Assist {
@ -567,7 +620,7 @@ pub fn test_some_range(a: int) -> bool {
}), }),
frange.into(), frange.into(),
); );
assert_eq!(3, assists.len()); assert_eq!(4, assists.len());
let mut assists = assists.into_iter(); let mut assists = assists.into_iter();
let extract_into_variable_assist = assists.next().unwrap(); let extract_into_variable_assist = assists.next().unwrap();
@ -578,7 +631,11 @@ pub fn test_some_range(a: int) -> bool {
RefactorExtract, RefactorExtract,
), ),
label: "Extract into variable", label: "Extract into variable",
group: None, group: Some(
GroupLabel(
"Extract into...",
),
),
target: 59..60, target: 59..60,
source_change: Some( source_change: Some(
SourceChange { SourceChange {
@ -637,7 +694,11 @@ pub fn test_some_range(a: int) -> bool {
RefactorExtract, RefactorExtract,
), ),
label: "Extract into constant", label: "Extract into constant",
group: None, group: Some(
GroupLabel(
"Extract into...",
),
),
target: 59..60, target: 59..60,
source_change: None, source_change: None,
command: None, command: None,
@ -645,6 +706,26 @@ pub fn test_some_range(a: int) -> bool {
"#]] "#]]
.assert_debug_eq(&extract_into_constant_assist); .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(); let extract_into_function_assist = assists.next().unwrap();
expect![[r#" expect![[r#"
Assist { Assist {
@ -664,7 +745,7 @@ pub fn test_some_range(a: int) -> bool {
{ {
let assists = assists(&db, &cfg, AssistResolveStrategy::All, frange.into()); 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 mut assists = assists.into_iter();
let extract_into_variable_assist = assists.next().unwrap(); let extract_into_variable_assist = assists.next().unwrap();
@ -675,7 +756,11 @@ pub fn test_some_range(a: int) -> bool {
RefactorExtract, RefactorExtract,
), ),
label: "Extract into variable", label: "Extract into variable",
group: None, group: Some(
GroupLabel(
"Extract into...",
),
),
target: 59..60, target: 59..60,
source_change: Some( source_change: Some(
SourceChange { SourceChange {
@ -734,7 +819,11 @@ pub fn test_some_range(a: int) -> bool {
RefactorExtract, RefactorExtract,
), ),
label: "Extract into constant", label: "Extract into constant",
group: None, group: Some(
GroupLabel(
"Extract into...",
),
),
target: 59..60, target: 59..60,
source_change: Some( source_change: Some(
SourceChange { SourceChange {
@ -789,6 +878,73 @@ pub fn test_some_range(a: int) -> bool {
"#]] "#]]
.assert_debug_eq(&extract_into_constant_assist); .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(); let extract_into_function_assist = assists.next().unwrap();
expect![[r#" expect![[r#"
Assist { Assist {

View file

@ -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] #[test]
fn doctest_extract_struct_from_enum_variant() { fn doctest_extract_struct_from_enum_variant() {
check_doc_test( check_doc_test(