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