aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorMikhail Rakhmanov <[email protected]>2020-05-22 21:28:30 +0100
committerMikhail Rakhmanov <[email protected]>2020-05-22 21:28:30 +0100
commit5cd4eb6dd6d8c733077a6aeea5d2cc0812ded096 (patch)
treedffc23552eb6e77c50966fa155ed8a56b418c5f0 /crates
parentd4daca9f02ec46be1beef79e9ed647a3a24e2434 (diff)
Add preliminary implementation of extract struct from enum variant
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/Cargo.toml1
-rw-r--r--crates/ra_assists/src/assist_context.rs62
-rw-r--r--crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs338
-rw-r--r--crates/ra_assists/src/lib.rs2
4 files changed, 402 insertions, 1 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
index 3bcf58ba4..f3481bdeb 100644
--- a/crates/ra_assists/Cargo.toml
+++ b/crates/ra_assists/Cargo.toml
@@ -20,5 +20,6 @@ ra_fmt = { path = "../ra_fmt" }
20ra_prof = { path = "../ra_prof" } 20ra_prof = { path = "../ra_prof" }
21ra_db = { path = "../ra_db" } 21ra_db = { path = "../ra_db" }
22ra_ide_db = { path = "../ra_ide_db" } 22ra_ide_db = { path = "../ra_ide_db" }
23hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" }
23hir = { path = "../ra_hir", package = "ra_hir" } 24hir = { path = "../ra_hir", package = "ra_hir" }
24test_utils = { path = "../test_utils" } 25test_utils = { path = "../test_utils" }
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
index 5b1a4680b..6291c68de 100644
--- a/crates/ra_assists/src/assist_context.rs
+++ b/crates/ra_assists/src/assist_context.rs
@@ -2,7 +2,7 @@
2 2
3use algo::find_covering_element; 3use algo::find_covering_element;
4use hir::Semantics; 4use hir::Semantics;
5use ra_db::{FileId, FileRange}; 5use ra_db::{FileId, FileRange, FilePosition};
6use ra_fmt::{leading_indent, reindent}; 6use ra_fmt::{leading_indent, reindent};
7use ra_ide_db::{ 7use ra_ide_db::{
8 source_change::{SourceChange, SourceFileEdit}, 8 source_change::{SourceChange, SourceFileEdit},
@@ -19,6 +19,7 @@ use crate::{
19 assist_config::{AssistConfig, SnippetCap}, 19 assist_config::{AssistConfig, SnippetCap},
20 Assist, AssistId, GroupLabel, ResolvedAssist, 20 Assist, AssistId, GroupLabel, ResolvedAssist,
21}; 21};
22use rustc_hash::FxHashMap;
22 23
23/// `AssistContext` allows to apply an assist or check if it could be applied. 24/// `AssistContext` allows to apply an assist or check if it could be applied.
24/// 25///
@@ -138,6 +139,16 @@ impl Assists {
138 let label = Assist::new(id, label.into(), None, target); 139 let label = Assist::new(id, label.into(), None, target);
139 self.add_impl(label, f) 140 self.add_impl(label, f)
140 } 141 }
142 pub(crate) fn add_in_multiple_files(
143 &mut self,
144 id: AssistId,
145 label: impl Into<String>,
146 target: TextRange,
147 f: impl FnOnce(&mut AssistDirector),
148 ) -> Option<()> {
149 let label = Assist::new(id, label.into(), None, target);
150 self.add_impl_multiple_files(label, f)
151 }
141 pub(crate) fn add_group( 152 pub(crate) fn add_group(
142 &mut self, 153 &mut self,
143 group: &GroupLabel, 154 group: &GroupLabel,
@@ -162,6 +173,27 @@ impl Assists {
162 Some(()) 173 Some(())
163 } 174 }
164 175
176 fn add_impl_multiple_files(&mut self, label: Assist, f: impl FnOnce(&mut AssistDirector)) -> Option<()> {
177 let change_label = label.label.clone();
178 if !self.resolve {
179 return None
180 }
181 let mut director = AssistDirector::new(change_label.clone());
182 f(&mut director);
183 let changes = director.finish();
184 let file_edits: Vec<SourceFileEdit> = changes.into_iter()
185 .map(|mut change| change.source_file_edits.pop().unwrap()).collect();
186
187 let source_change = SourceChange {
188 source_file_edits: file_edits,
189 file_system_edits: vec![],
190 is_snippet: false,
191 };
192
193 self.buf.push((label, Some(source_change)));
194 Some(())
195 }
196
165 fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> { 197 fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> {
166 self.buf.sort_by_key(|(label, _edit)| label.target.len()); 198 self.buf.sort_by_key(|(label, _edit)| label.target.len());
167 self.buf 199 self.buf
@@ -255,3 +287,31 @@ impl AssistBuilder {
255 res 287 res
256 } 288 }
257} 289}
290
291pub(crate) struct AssistDirector {
292 source_changes: Vec<SourceChange>,
293 builders: FxHashMap<FileId, AssistBuilder>,
294 change_label: String
295}
296
297impl AssistDirector {
298 fn new(change_label: String) -> AssistDirector {
299 AssistDirector {
300 source_changes: vec![],
301 builders: FxHashMap::default(),
302 change_label
303 }
304 }
305
306 pub(crate) fn perform(&mut self, file_id: FileId, f: impl FnOnce(&mut AssistBuilder)) {
307 let mut builder = self.builders.entry(file_id).or_insert(AssistBuilder::new(file_id));
308 f(&mut builder);
309 }
310
311 fn finish(mut self) -> Vec<SourceChange> {
312 for (file_id, builder) in self.builders.into_iter().collect::<Vec<(FileId, AssistBuilder)>>() {
313 self.source_changes.push(builder.finish());
314 }
315 self.source_changes
316 }
317}
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
new file mode 100644
index 000000000..6e19a6feb
--- /dev/null
+++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
@@ -0,0 +1,338 @@
1use hir_expand::name::AsName;
2use ra_ide_db::{
3 defs::Definition, imports_locator::ImportsLocator, search::Reference, RootDatabase,
4};
5use ra_syntax::{
6 algo::find_node_at_offset,
7 ast::{self, AstNode, NameOwner},
8 SourceFile, SyntaxNode, TextRange, TextSize,
9};
10use stdx::format_to;
11
12use crate::{
13 assist_context::{AssistBuilder, AssistDirector},
14 utils::insert_use_statement,
15 AssistContext, AssistId, Assists,
16};
17use ast::{ArgListOwner, VisibilityOwner};
18use hir::{EnumVariant, Module, ModuleDef};
19use ra_fmt::leading_indent;
20use rustc_hash::FxHashSet;
21use ra_db::FileId;
22
23// Assist extract_struct_from_enum
24//
25// Extracts a from struct from enum variant
26//
27// ```
28// enum A { <|>One(u32, u32) }
29// ```
30// ->
31// ```
32// struct One(pub u32, pub u32);
33//
34// enum A { One(One) }"
35// ```
36pub(crate) fn extract_struct_from_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
37 let variant = ctx.find_node_at_offset::<ast::EnumVariant>()?;
38 let field_list = match variant.kind() {
39 ast::StructKind::Tuple(field_list) => field_list,
40 _ => return None,
41 };
42 let variant_name = variant.name()?.to_string();
43 let enum_ast = variant.parent_enum();
44 let enum_name = enum_ast.name().unwrap().to_string();
45 let visibility = enum_ast.visibility();
46 let variant_hir = ctx.sema.to_def(&variant)?;
47
48 if existing_struct_def(ctx.db, &variant_name, &variant_hir) {
49 return None;
50 }
51
52 let target = variant.syntax().text_range();
53 return acc.add_in_multiple_files(
54 AssistId("extract_struct_from_enum_variant"),
55 "Extract struct from enum variant",
56 target,
57 |edit| {
58 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
59 let res = definition.find_usages(&ctx.db, None);
60 let module_def = mod_def_for_target_module(ctx, &enum_name);
61 let start_offset = variant.parent_enum().syntax().text_range().start();
62 let mut seen_files_map: FxHashSet<Module> = FxHashSet::default();
63 seen_files_map.insert(module_def.module(ctx.db).unwrap());
64 for reference in res {
65 let source_file = ctx.sema.parse(reference.file_range.file_id);
66 update_reference(
67 ctx,
68 edit,
69 reference,
70 &source_file,
71 &module_def,
72 &mut seen_files_map,
73 );
74 }
75 extract_struct_def(
76 edit,
77 enum_ast.syntax(),
78 &variant_name,
79 &field_list.to_string(),
80 start_offset,
81 ctx.frange.file_id,
82 &visibility,
83 );
84 let list_range = field_list.syntax().text_range();
85 update_variant(edit, &variant_name, ctx.frange.file_id, list_range);
86 },
87 );
88}
89
90fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
91 let module_defs = variant.parent_enum(db).module(db).scope(db, None);
92 for (name, _) in module_defs {
93 if name.to_string() == variant_name.to_string() {
94 return true;
95 }
96 }
97 false
98}
99
100fn mod_def_for_target_module(ctx: &AssistContext, enum_name: &str) -> ModuleDef {
101 ImportsLocator::new(ctx.db).find_imports(enum_name).first().unwrap().left().unwrap()
102}
103
104fn insert_use_import(
105 ctx: &AssistContext,
106 builder: &mut AssistBuilder,
107 path: &ast::PathExpr,
108 module: &Module,
109 module_def: &ModuleDef,
110 path_segment: ast::NameRef,
111) -> Option<()> {
112 let db = ctx.db;
113 let mod_path = module.find_use_path(db, module_def.clone());
114 if let Some(mut mod_path) = mod_path {
115 mod_path.segments.pop();
116 mod_path.segments.push(path_segment.as_name());
117 insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder());
118 }
119 Some(())
120}
121
122fn extract_struct_def(
123 edit: &mut AssistDirector,
124 enum_ast: &SyntaxNode,
125 variant_name: &str,
126 variant_list: &str,
127 start_offset: TextSize,
128 file_id: FileId,
129 visibility: &Option<ast::Visibility>,
130) -> Option<()> {
131 let visibility_string = if let Some(visibility) = visibility {
132 format!("{} ", visibility.to_string())
133 } else {
134 "".to_string()
135 };
136 let mut buf = String::new();
137 let indent = if let Some(indent) = leading_indent(enum_ast) {
138 indent.to_string()
139 } else {
140 "".to_string()
141 };
142
143 format_to!(
144 buf,
145 r#"{}struct {}{};
146
147{}"#,
148 visibility_string,
149 variant_name,
150 list_with_visibility(variant_list),
151 indent
152 );
153 edit.perform(file_id, |builder| {
154 builder.insert(start_offset, buf);
155 });
156 Some(())
157}
158
159fn update_variant(
160 edit: &mut AssistDirector,
161 variant_name: &str,
162 file_id: FileId,
163 list_range: TextRange,
164) -> Option<()> {
165 let inside_variant_range = TextRange::new(
166 list_range.start().checked_add(TextSize::from(1))?,
167 list_range.end().checked_sub(TextSize::from(1))?,
168 );
169 edit.perform(file_id, |builder| {
170 builder.set_file(file_id);
171 builder.replace(inside_variant_range, variant_name);
172 });
173 Some(())
174}
175
176fn update_reference(
177 ctx: &AssistContext,
178 edit: &mut AssistDirector,
179 reference: Reference,
180 source_file: &SourceFile,
181 module_def: &ModuleDef,
182 seen_files_map: &mut FxHashSet<Module>,
183) -> Option<()> {
184 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
185 source_file.syntax(),
186 reference.file_range.range.start(),
187 )?;
188 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
189 let list = call.arg_list()?;
190 let segment = path_expr.path()?.segment()?;
191 let list_range = list.syntax().text_range();
192 let inside_list_range = TextRange::new(
193 list_range.start().checked_add(TextSize::from(1))?,
194 list_range.end().checked_sub(TextSize::from(1))?,
195 );
196 edit.perform(reference.file_range.file_id, |builder| {
197 let module = ctx.sema.scope(&path_expr.syntax()).module().unwrap();
198 if !seen_files_map.contains(&module) {
199 if insert_use_import(
200 ctx,
201 builder,
202 &path_expr,
203 &module,
204 module_def,
205 segment.name_ref().unwrap(),
206 )
207 .is_some()
208 {
209 seen_files_map.insert(module);
210 }
211 }
212 builder.replace(inside_list_range, format!("{}{}", segment, list));
213 });
214 Some(())
215}
216
217fn list_with_visibility(list: &str) -> String {
218 list.split(',')
219 .map(|part| {
220 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
221 let mut mod_part = part.trim().to_string();
222 mod_part.insert_str(index, "pub ");
223 mod_part
224 })
225 .collect::<Vec<String>>()
226 .join(", ")
227}
228
229#[cfg(test)]
230mod tests {
231
232 use crate::{utils::FamousDefs, tests::{check_assist, check_assist_not_applicable}};
233
234 use super::*;
235
236 #[test]
237 fn test_extract_struct_several_fields() {
238 check_assist(
239 extract_struct_from_enum,
240 "enum A { <|>One(u32, u32) }",
241 r#"struct One(pub u32, pub u32);
242
243enum A { One(One) }"#,
244 );
245 }
246
247 #[test]
248 fn test_extract_struct_one_field() {
249 check_assist(
250 extract_struct_from_enum,
251 "enum A { <|>One(u32) }",
252 r#"struct One(pub u32);
253
254enum A { One(One) }"#,
255 );
256 }
257
258 #[test]
259 fn test_extract_struct_pub_visibility() {
260 check_assist(
261 extract_struct_from_enum,
262 "pub enum A { <|>One(u32, u32) }",
263 r#"pub struct One(pub u32, pub u32);
264
265pub enum A { One(One) }"#,
266 );
267 }
268
269 #[test]
270 fn test_extract_struct_with_complex_imports() {
271 check_assist(
272 extract_struct_from_enum,
273 r#"mod my_mod {
274 fn another_fn() {
275 let m = my_other_mod::MyEnum::MyField(1, 1);
276 }
277
278 pub mod my_other_mod {
279 fn another_fn() {
280 let m = MyEnum::MyField(1, 1);
281 }
282
283 pub enum MyEnum {
284 <|>MyField(u8, u8),
285 }
286 }
287}
288
289fn another_fn() {
290 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
291}"#,
292 r#"use my_mod::my_other_mod::MyField;
293
294mod my_mod {
295 use my_other_mod::MyField;
296
297 fn another_fn() {
298 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
299 }
300
301 pub mod my_other_mod {
302 fn another_fn() {
303 let m = MyEnum::MyField(MyField(1, 1));
304 }
305
306 pub struct MyField(pub u8, pub u8);
307
308 pub enum MyEnum {
309 MyField(MyField),
310 }
311 }
312}
313
314fn another_fn() {
315 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
316}"#,
317 );
318 }
319
320 fn check_not_applicable(ra_fixture: &str) {
321 let fixture =
322 format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
323 check_assist_not_applicable(extract_struct_from_enum, &fixture)
324 }
325
326 #[test]
327 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
328 check_not_applicable("enum A { <|>One }");
329 }
330
331 #[test]
332 fn test_extract_enum_not_applicable_if_struct_exists() {
333 check_not_applicable(
334 r#"struct One;
335 enum A { <|>One(u8) }"#,
336 );
337 }
338}
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 464bc03dd..9933f7a50 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -115,6 +115,7 @@ mod handlers {
115 mod change_return_type_to_result; 115 mod change_return_type_to_result;
116 mod change_visibility; 116 mod change_visibility;
117 mod early_return; 117 mod early_return;
118 mod extract_struct_from_enum_variant;
118 mod fill_match_arms; 119 mod fill_match_arms;
119 mod fix_visibility; 120 mod fix_visibility;
120 mod flip_binexpr; 121 mod flip_binexpr;
@@ -154,6 +155,7 @@ mod handlers {
154 change_return_type_to_result::change_return_type_to_result, 155 change_return_type_to_result::change_return_type_to_result,
155 change_visibility::change_visibility, 156 change_visibility::change_visibility,
156 early_return::convert_to_guarded_return, 157 early_return::convert_to_guarded_return,
158 extract_struct_from_enum_variant::extract_struct_from_enum,
157 fill_match_arms::fill_match_arms, 159 fill_match_arms::fill_match_arms,
158 fix_visibility::fix_visibility, 160 fix_visibility::fix_visibility,
159 flip_binexpr::flip_binexpr, 161 flip_binexpr::flip_binexpr,