1 use hir::{EnumVariant, Module, ModuleDef, Name};
3 use ra_fmt::leading_indent;
4 use ra_ide_db::{defs::Definition, search::Reference, RootDatabase};
6 algo::find_node_at_offset,
7 ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
8 SourceFile, SyntaxNode, TextRange, TextSize,
10 use rustc_hash::FxHashSet;
13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, Assists,
16 // Assist: extract_struct_from_enum_variant
18 // Extracts a struct from enum variant.
21 // enum A { <|>One(u32, u32) }
25 // struct One(pub u32, pub u32);
27 // enum A { One(One) }
29 pub(crate) fn extract_struct_from_enum_variant(
33 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
34 let field_list = match variant.kind() {
35 ast::StructKind::Tuple(field_list) => field_list,
38 let variant_name = variant.name()?.to_string();
39 let variant_hir = ctx.sema.to_def(&variant)?;
40 if existing_struct_def(ctx.db, &variant_name, &variant_hir) {
43 let enum_ast = variant.parent_enum();
44 let visibility = enum_ast.visibility();
45 let enum_hir = ctx.sema.to_def(&enum_ast)?;
46 let variant_hir_name = variant_hir.name(ctx.db);
47 let enum_module_def = ModuleDef::from(enum_hir);
48 let current_module = enum_hir.module(ctx.db);
49 let target = variant.syntax().text_range();
51 AssistId("extract_struct_from_enum_variant"),
52 "Extract struct from enum variant",
55 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
56 let res = definition.find_usages(&ctx.db, None);
57 let start_offset = variant.parent_enum().syntax().text_range().start();
58 let mut visited_modules_set = FxHashSet::default();
59 visited_modules_set.insert(current_module);
60 for reference in res {
61 let source_file = ctx.sema.parse(reference.file_range.file_id);
69 &mut visited_modules_set,
76 &field_list.to_string(),
81 let list_range = field_list.syntax().text_range();
82 update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
87 fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
93 .any(|(name, _)| name.to_string() == variant_name.to_string())
98 builder: &mut AssistBuilder,
101 enum_module_def: &ModuleDef,
102 variant_hir_name: &Name,
105 let mod_path = module.find_use_path(db, enum_module_def.clone());
106 if let Some(mut mod_path) = mod_path {
107 mod_path.segments.pop();
108 mod_path.segments.push(variant_hir_name.clone());
109 insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder());
114 fn extract_struct_def(
115 builder: &mut AssistBuilder,
116 enum_ast: &SyntaxNode,
119 start_offset: TextSize,
121 visibility: &Option<ast::Visibility>,
123 let visibility_string = if let Some(visibility) = visibility {
124 format!("{} ", visibility.to_string())
128 let indent = if let Some(indent) = leading_indent(enum_ast) {
133 let struct_def = format!(
139 list_with_visibility(variant_list),
142 builder.edit_file(file_id);
143 builder.insert(start_offset, struct_def);
148 builder: &mut AssistBuilder,
151 list_range: TextRange,
153 let inside_variant_range = TextRange::new(
154 list_range.start().checked_add(TextSize::from(1))?,
155 list_range.end().checked_sub(TextSize::from(1))?,
157 builder.edit_file(file_id);
158 builder.replace(inside_variant_range, variant_name);
164 builder: &mut AssistBuilder,
165 reference: Reference,
166 source_file: &SourceFile,
167 enum_module_def: &ModuleDef,
168 variant_hir_name: &Name,
169 visited_modules_set: &mut FxHashSet<Module>,
171 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
172 source_file.syntax(),
173 reference.file_range.range.start(),
175 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
176 let list = call.arg_list()?;
177 let segment = path_expr.path()?.segment()?;
178 let module = ctx.sema.scope(&path_expr.syntax()).module()?;
179 let list_range = list.syntax().text_range();
180 let inside_list_range = TextRange::new(
181 list_range.start().checked_add(TextSize::from(1))?,
182 list_range.end().checked_sub(TextSize::from(1))?,
184 builder.edit_file(reference.file_range.file_id);
185 if !visited_modules_set.contains(&module) {
186 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
189 visited_modules_set.insert(module);
192 builder.replace(inside_list_range, format!("{}{}", segment, list));
196 fn list_with_visibility(list: &str) -> String {
199 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
200 let mut mod_part = part.trim().to_string();
201 mod_part.insert_str(index, "pub ");
204 .collect::<Vec<String>>()
212 tests::{check_assist, check_assist_not_applicable},
219 fn test_extract_struct_several_fields() {
221 extract_struct_from_enum_variant,
222 "enum A { <|>One(u32, u32) }",
223 r#"struct One(pub u32, pub u32);
225 enum A { One(One) }"#,
230 fn test_extract_struct_one_field() {
232 extract_struct_from_enum_variant,
233 "enum A { <|>One(u32) }",
234 r#"struct One(pub u32);
236 enum A { One(One) }"#,
241 fn test_extract_struct_pub_visibility() {
243 extract_struct_from_enum_variant,
244 "pub enum A { <|>One(u32, u32) }",
245 r#"pub struct One(pub u32, pub u32);
247 pub enum A { One(One) }"#,
252 fn test_extract_struct_with_complex_imports() {
254 extract_struct_from_enum_variant,
257 let m = my_other_mod::MyEnum::MyField(1, 1);
260 pub mod my_other_mod {
262 let m = MyEnum::MyField(1, 1);
272 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
274 r#"use my_mod::my_other_mod::MyField;
277 use my_other_mod::MyField;
280 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
283 pub mod my_other_mod {
285 let m = MyEnum::MyField(MyField(1, 1));
288 pub struct MyField(pub u8, pub u8);
297 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
302 fn check_not_applicable(ra_fixture: &str) {
304 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
305 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
309 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
310 check_not_applicable("enum A { <|>One }");
314 fn test_extract_enum_not_applicable_if_struct_exists() {
315 check_not_applicable(
317 enum A { <|>One(u8) }"#,