rust/crates/assists/src/handlers/extract_struct_from_enum_variant.rs

310 lines
9.0 KiB
Rust
Raw Normal View History

use hir::{AsName, EnumVariant, Module, ModuleDef, Name};
2020-08-13 09:39:16 -05:00
use ide_db::{defs::Definition, search::Reference, RootDatabase};
use rustc_hash::{FxHashMap, FxHashSet};
2020-08-12 11:26:51 -05:00
use syntax::{
algo::find_node_at_offset,
algo::SyntaxRewriter,
ast::{self, edit::IndentLevel, make, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
SourceFile, SyntaxElement,
};
use crate::{
utils::{insert_use, mod_path_to_ast, ImportScope},
AssistContext, AssistId, AssistKind, Assists,
};
2020-06-03 13:43:57 -05:00
// Assist: extract_struct_from_enum_variant
//
2020-06-03 13:43:57 -05:00
// Extracts a struct from enum variant.
//
// ```
// enum A { <|>One(u32, u32) }
// ```
// ->
// ```
// struct One(pub u32, pub u32);
//
2020-06-03 13:43:57 -05:00
// enum A { One(One) }
// ```
2020-06-03 13:43:57 -05:00
pub(crate) fn extract_struct_from_enum_variant(
acc: &mut Assists,
ctx: &AssistContext,
) -> Option<()> {
2020-07-30 10:56:53 -05:00
let variant = ctx.find_node_at_offset::<ast::Variant>()?;
let field_list = match variant.kind() {
ast::StructKind::Tuple(field_list) => field_list,
_ => return None,
};
// skip 1-tuple variants
if field_list.fields().count() == 1 {
return None;
}
let variant_name = variant.name()?;
let variant_hir = ctx.sema.to_def(&variant)?;
2020-07-01 02:14:23 -05:00
if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
return None;
}
2020-05-23 04:53:02 -05:00
let enum_ast = variant.parent_enum();
let visibility = enum_ast.visibility();
2020-06-05 06:17:17 -05:00
let enum_hir = ctx.sema.to_def(&enum_ast)?;
2020-07-01 02:14:23 -05:00
let variant_hir_name = variant_hir.name(ctx.db());
2020-06-05 06:17:17 -05:00
let enum_module_def = ModuleDef::from(enum_hir);
2020-07-01 02:14:23 -05:00
let current_module = enum_hir.module(ctx.db());
let target = variant.syntax().text_range();
2020-06-08 17:01:40 -05:00
acc.add(
2020-07-02 16:48:35 -05:00
AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
"Extract struct from enum variant",
target,
2020-06-08 17:01:40 -05:00
|builder| {
let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
let res = definition.usages(&ctx.sema).all();
let mut visited_modules_set = FxHashSet::default();
2020-05-23 04:53:02 -05:00
visited_modules_set.insert(current_module);
let mut rewriters = FxHashMap::default();
for reference in res {
let rewriter = rewriters
.entry(reference.file_range.file_id)
.or_insert_with(SyntaxRewriter::default);
let source_file = ctx.sema.parse(reference.file_range.file_id);
update_reference(
ctx,
rewriter,
reference,
&source_file,
2020-06-05 06:17:17 -05:00
&enum_module_def,
&variant_hir_name,
&mut visited_modules_set,
);
}
let mut rewriter =
rewriters.remove(&ctx.frange.file_id).unwrap_or_else(SyntaxRewriter::default);
for (file_id, rewriter) in rewriters {
builder.edit_file(file_id);
builder.rewrite(rewriter);
}
builder.edit_file(ctx.frange.file_id);
update_variant(&mut rewriter, &variant_name, &field_list);
extract_struct_def(
&mut rewriter,
2020-08-13 04:47:31 -05:00
&enum_ast,
variant_name.clone(),
&field_list,
&variant.parent_enum().syntax().clone().into(),
visibility,
);
builder.rewrite(rewriter);
},
2020-06-05 04:45:41 -05:00
)
}
fn existing_struct_def(db: &RootDatabase, variant_name: &ast::Name, variant: &EnumVariant) -> bool {
2020-05-22 18:41:08 -05:00
variant
.parent_enum(db)
.module(db)
.scope(db, None)
.into_iter()
.any(|(name, _)| name == variant_name.as_name())
}
fn insert_import(
ctx: &AssistContext,
rewriter: &mut SyntaxRewriter,
path: &ast::PathExpr,
module: &Module,
2020-06-05 06:17:17 -05:00
enum_module_def: &ModuleDef,
variant_hir_name: &Name,
) -> Option<()> {
2020-07-01 02:14:23 -05:00
let db = ctx.db();
2020-06-05 06:17:17 -05:00
let mod_path = module.find_use_path(db, enum_module_def.clone());
if let Some(mut mod_path) = mod_path {
mod_path.segments.pop();
2020-06-05 06:17:17 -05:00
mod_path.segments.push(variant_hir_name.clone());
let scope = ImportScope::find_insert_use_container(path.syntax(), ctx)?;
*rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
}
Some(())
}
fn extract_struct_def(
rewriter: &mut SyntaxRewriter,
2020-08-13 04:47:31 -05:00
enum_: &ast::Enum,
variant_name: ast::Name,
variant_list: &ast::TupleFieldList,
start_offset: &SyntaxElement,
visibility: Option<ast::Visibility>,
) -> Option<()> {
let variant_list = make::tuple_field_list(
variant_list
.fields()
.flat_map(|field| Some(make::tuple_field(Some(make::visibility_pub()), field.ty()?))),
);
rewriter.insert_before(
start_offset,
make::struct_(visibility, variant_name, None, variant_list.into()).syntax(),
);
rewriter.insert_before(start_offset, &make::tokens::blank_line());
if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
rewriter
.insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
}
Some(())
}
fn update_variant(
rewriter: &mut SyntaxRewriter,
variant_name: &ast::Name,
field_list: &ast::TupleFieldList,
) -> Option<()> {
let (l, r): (SyntaxElement, SyntaxElement) =
(field_list.l_paren_token()?.into(), field_list.r_paren_token()?.into());
let replacement = vec![l, variant_name.syntax().clone().into(), r];
rewriter.replace_with_many(field_list.syntax(), replacement);
Some(())
}
fn update_reference(
ctx: &AssistContext,
rewriter: &mut SyntaxRewriter,
reference: Reference,
source_file: &SourceFile,
enum_module_def: &ModuleDef,
variant_hir_name: &Name,
visited_modules_set: &mut FxHashSet<Module>,
) -> Option<()> {
let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
source_file.syntax(),
reference.file_range.range.start(),
)?;
let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
let list = call.arg_list()?;
let segment = path_expr.path()?.segment()?;
let module = ctx.sema.scope(&path_expr.syntax()).module()?;
2020-06-08 17:01:40 -05:00
if !visited_modules_set.contains(&module) {
if insert_import(ctx, rewriter, &path_expr, &module, enum_module_def, variant_hir_name)
2020-06-08 17:01:40 -05:00
.is_some()
{
visited_modules_set.insert(module);
}
2020-06-08 17:01:40 -05:00
}
let lparen = syntax::SyntaxElement::from(list.l_paren_token()?);
let rparen = syntax::SyntaxElement::from(list.r_paren_token()?);
rewriter.insert_after(&lparen, segment.syntax());
rewriter.insert_after(&lparen, &lparen);
rewriter.insert_before(&rparen, &rparen);
Some(())
}
#[cfg(test)]
mod tests {
2020-05-22 15:43:52 -05:00
use crate::{
tests::{check_assist, check_assist_not_applicable},
utils::FamousDefs,
};
use super::*;
#[test]
fn test_extract_struct_several_fields() {
check_assist(
2020-06-03 13:43:57 -05:00
extract_struct_from_enum_variant,
"enum A { <|>One(u32, u32) }",
r#"struct One(pub u32, pub u32);
enum A { One(One) }"#,
);
}
#[test]
fn test_extract_struct_pub_visibility() {
check_assist(
2020-06-03 13:43:57 -05:00
extract_struct_from_enum_variant,
"pub enum A { <|>One(u32, u32) }",
r#"pub struct One(pub u32, pub u32);
pub enum A { One(One) }"#,
);
}
#[test]
fn test_extract_struct_with_complex_imports() {
check_assist(
2020-06-03 13:43:57 -05:00
extract_struct_from_enum_variant,
r#"mod my_mod {
fn another_fn() {
let m = my_other_mod::MyEnum::MyField(1, 1);
}
pub mod my_other_mod {
fn another_fn() {
let m = MyEnum::MyField(1, 1);
}
pub enum MyEnum {
<|>MyField(u8, u8),
}
}
}
fn another_fn() {
let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
}"#,
r#"use my_mod::my_other_mod::MyField;
mod my_mod {
use my_other_mod::MyField;
fn another_fn() {
let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
}
pub mod my_other_mod {
fn another_fn() {
let m = MyEnum::MyField(MyField(1, 1));
}
pub struct MyField(pub u8, pub u8);
pub enum MyEnum {
MyField(MyField),
}
}
}
fn another_fn() {
let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
}"#,
);
}
fn check_not_applicable(ra_fixture: &str) {
let fixture =
format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
2020-06-03 13:43:57 -05:00
check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
}
#[test]
fn test_extract_enum_not_applicable_for_element_with_no_fields() {
check_not_applicable("enum A { <|>One }");
}
#[test]
fn test_extract_enum_not_applicable_if_struct_exists() {
check_not_applicable(
r#"struct One;
enum A { <|>One(u8) }"#,
);
}
#[test]
fn test_extract_not_applicable_one_field() {
check_not_applicable(r"enum A { <|>One(u32) }");
}
}