diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-11-09 12:28:57 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2020-11-09 12:28:57 +0000 |
commit | 73b08131dff8855c6736ce41867a6a197dfb87af (patch) | |
tree | 9ee0820ea9c84dd40c761586cf7efe3ff0f77abc /crates/assists/src/handlers/replace_derive_with_manual_impl.rs | |
parent | 2d3b0571bb02aa85e3aae390e5cfb10b0ada5c38 (diff) | |
parent | d31ce3b16cbd23950197d8992720b431e19b6af1 (diff) |
Merge #6506
6506: Cleanup assists r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/assists/src/handlers/replace_derive_with_manual_impl.rs')
-rw-r--r-- | crates/assists/src/handlers/replace_derive_with_manual_impl.rs | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs new file mode 100644 index 000000000..82625516c --- /dev/null +++ b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs | |||
@@ -0,0 +1,398 @@ | |||
1 | use ide_db::imports_locator; | ||
2 | use itertools::Itertools; | ||
3 | use syntax::{ | ||
4 | ast::{self, make, AstNode}, | ||
5 | Direction, SmolStr, | ||
6 | SyntaxKind::{IDENT, WHITESPACE}, | ||
7 | TextSize, | ||
8 | }; | ||
9 | |||
10 | use crate::{ | ||
11 | assist_context::{AssistBuilder, AssistContext, Assists}, | ||
12 | utils::{ | ||
13 | add_trait_assoc_items_to_impl, filter_assoc_items, mod_path_to_ast, render_snippet, Cursor, | ||
14 | DefaultMethods, | ||
15 | }, | ||
16 | AssistId, AssistKind, | ||
17 | }; | ||
18 | |||
19 | // Assist: replace_derive_with_manual_impl | ||
20 | // | ||
21 | // Converts a `derive` impl into a manual one. | ||
22 | // | ||
23 | // ``` | ||
24 | // # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; } | ||
25 | // #[derive(Deb<|>ug, Display)] | ||
26 | // struct S; | ||
27 | // ``` | ||
28 | // -> | ||
29 | // ``` | ||
30 | // # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; } | ||
31 | // #[derive(Display)] | ||
32 | // struct S; | ||
33 | // | ||
34 | // impl Debug for S { | ||
35 | // fn fmt(&self, f: &mut Formatter) -> Result<()> { | ||
36 | // ${0:todo!()} | ||
37 | // } | ||
38 | // } | ||
39 | // ``` | ||
40 | pub(crate) fn replace_derive_with_manual_impl( | ||
41 | acc: &mut Assists, | ||
42 | ctx: &AssistContext, | ||
43 | ) -> Option<()> { | ||
44 | let attr = ctx.find_node_at_offset::<ast::Attr>()?; | ||
45 | |||
46 | let attr_name = attr | ||
47 | .syntax() | ||
48 | .descendants_with_tokens() | ||
49 | .filter(|t| t.kind() == IDENT) | ||
50 | .find_map(syntax::NodeOrToken::into_token) | ||
51 | .filter(|t| t.text() == "derive")? | ||
52 | .text() | ||
53 | .clone(); | ||
54 | |||
55 | let trait_token = | ||
56 | ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?; | ||
57 | let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text()))); | ||
58 | |||
59 | let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?; | ||
60 | let insert_pos = annotated_name.syntax().parent()?.text_range().end(); | ||
61 | |||
62 | let current_module = ctx.sema.scope(annotated_name.syntax()).module()?; | ||
63 | let current_crate = current_module.krate(); | ||
64 | |||
65 | let found_traits = imports_locator::find_imports(&ctx.sema, current_crate, trait_token.text()) | ||
66 | .into_iter() | ||
67 | .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate { | ||
68 | either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_), | ||
69 | _ => None, | ||
70 | }) | ||
71 | .flat_map(|trait_| { | ||
72 | current_module | ||
73 | .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_)) | ||
74 | .as_ref() | ||
75 | .map(mod_path_to_ast) | ||
76 | .zip(Some(trait_)) | ||
77 | }); | ||
78 | |||
79 | let mut no_traits_found = true; | ||
80 | for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) { | ||
81 | add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?; | ||
82 | } | ||
83 | if no_traits_found { | ||
84 | add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?; | ||
85 | } | ||
86 | Some(()) | ||
87 | } | ||
88 | |||
89 | fn add_assist( | ||
90 | acc: &mut Assists, | ||
91 | ctx: &AssistContext, | ||
92 | attr: &ast::Attr, | ||
93 | trait_path: &ast::Path, | ||
94 | trait_: Option<hir::Trait>, | ||
95 | annotated_name: &ast::Name, | ||
96 | insert_pos: TextSize, | ||
97 | ) -> Option<()> { | ||
98 | let target = attr.syntax().text_range(); | ||
99 | let input = attr.token_tree()?; | ||
100 | let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name); | ||
101 | let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?; | ||
102 | |||
103 | acc.add( | ||
104 | AssistId("replace_derive_with_manual_impl", AssistKind::Refactor), | ||
105 | label, | ||
106 | target, | ||
107 | |builder| { | ||
108 | let impl_def_with_items = | ||
109 | impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path); | ||
110 | update_attribute(builder, &input, &trait_name, &attr); | ||
111 | match (ctx.config.snippet_cap, impl_def_with_items) { | ||
112 | (None, _) => builder.insert( | ||
113 | insert_pos, | ||
114 | format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name), | ||
115 | ), | ||
116 | (Some(cap), None) => builder.insert_snippet( | ||
117 | cap, | ||
118 | insert_pos, | ||
119 | format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name), | ||
120 | ), | ||
121 | (Some(cap), Some((impl_def, first_assoc_item))) => { | ||
122 | let mut cursor = Cursor::Before(first_assoc_item.syntax()); | ||
123 | let placeholder; | ||
124 | if let ast::AssocItem::Fn(ref func) = first_assoc_item { | ||
125 | if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) | ||
126 | { | ||
127 | if m.syntax().text() == "todo!()" { | ||
128 | placeholder = m; | ||
129 | cursor = Cursor::Replace(placeholder.syntax()); | ||
130 | } | ||
131 | } | ||
132 | } | ||
133 | |||
134 | builder.insert_snippet( | ||
135 | cap, | ||
136 | insert_pos, | ||
137 | format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)), | ||
138 | ) | ||
139 | } | ||
140 | }; | ||
141 | }, | ||
142 | ) | ||
143 | } | ||
144 | |||
145 | fn impl_def_from_trait( | ||
146 | sema: &hir::Semantics<ide_db::RootDatabase>, | ||
147 | annotated_name: &ast::Name, | ||
148 | trait_: Option<hir::Trait>, | ||
149 | trait_path: &ast::Path, | ||
150 | ) -> Option<(ast::Impl, ast::AssocItem)> { | ||
151 | let trait_ = trait_?; | ||
152 | let target_scope = sema.scope(annotated_name.syntax()); | ||
153 | let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No); | ||
154 | if trait_items.is_empty() { | ||
155 | return None; | ||
156 | } | ||
157 | let impl_def = make::impl_trait( | ||
158 | trait_path.clone(), | ||
159 | make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))), | ||
160 | ); | ||
161 | let (impl_def, first_assoc_item) = | ||
162 | add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope); | ||
163 | Some((impl_def, first_assoc_item)) | ||
164 | } | ||
165 | |||
166 | fn update_attribute( | ||
167 | builder: &mut AssistBuilder, | ||
168 | input: &ast::TokenTree, | ||
169 | trait_name: &ast::NameRef, | ||
170 | attr: &ast::Attr, | ||
171 | ) { | ||
172 | let new_attr_input = input | ||
173 | .syntax() | ||
174 | .descendants_with_tokens() | ||
175 | .filter(|t| t.kind() == IDENT) | ||
176 | .filter_map(|t| t.into_token().map(|t| t.text().clone())) | ||
177 | .filter(|t| t != trait_name.text()) | ||
178 | .collect::<Vec<SmolStr>>(); | ||
179 | let has_more_derives = !new_attr_input.is_empty(); | ||
180 | |||
181 | if has_more_derives { | ||
182 | let new_attr_input = format!("({})", new_attr_input.iter().format(", ")); | ||
183 | builder.replace(input.syntax().text_range(), new_attr_input); | ||
184 | } else { | ||
185 | let attr_range = attr.syntax().text_range(); | ||
186 | builder.delete(attr_range); | ||
187 | |||
188 | if let Some(line_break_range) = attr | ||
189 | .syntax() | ||
190 | .next_sibling_or_token() | ||
191 | .filter(|t| t.kind() == WHITESPACE) | ||
192 | .map(|t| t.text_range()) | ||
193 | { | ||
194 | builder.delete(line_break_range); | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | |||
199 | #[cfg(test)] | ||
200 | mod tests { | ||
201 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
202 | |||
203 | use super::*; | ||
204 | |||
205 | #[test] | ||
206 | fn add_custom_impl_debug() { | ||
207 | check_assist( | ||
208 | replace_derive_with_manual_impl, | ||
209 | " | ||
210 | mod fmt { | ||
211 | pub struct Error; | ||
212 | pub type Result = Result<(), Error>; | ||
213 | pub struct Formatter<'a>; | ||
214 | pub trait Debug { | ||
215 | fn fmt(&self, f: &mut Formatter<'_>) -> Result; | ||
216 | } | ||
217 | } | ||
218 | |||
219 | #[derive(Debu<|>g)] | ||
220 | struct Foo { | ||
221 | bar: String, | ||
222 | } | ||
223 | ", | ||
224 | " | ||
225 | mod fmt { | ||
226 | pub struct Error; | ||
227 | pub type Result = Result<(), Error>; | ||
228 | pub struct Formatter<'a>; | ||
229 | pub trait Debug { | ||
230 | fn fmt(&self, f: &mut Formatter<'_>) -> Result; | ||
231 | } | ||
232 | } | ||
233 | |||
234 | struct Foo { | ||
235 | bar: String, | ||
236 | } | ||
237 | |||
238 | impl fmt::Debug for Foo { | ||
239 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
240 | ${0:todo!()} | ||
241 | } | ||
242 | } | ||
243 | ", | ||
244 | ) | ||
245 | } | ||
246 | #[test] | ||
247 | fn add_custom_impl_all() { | ||
248 | check_assist( | ||
249 | replace_derive_with_manual_impl, | ||
250 | " | ||
251 | mod foo { | ||
252 | pub trait Bar { | ||
253 | type Qux; | ||
254 | const Baz: usize = 42; | ||
255 | const Fez: usize; | ||
256 | fn foo(); | ||
257 | fn bar() {} | ||
258 | } | ||
259 | } | ||
260 | |||
261 | #[derive(<|>Bar)] | ||
262 | struct Foo { | ||
263 | bar: String, | ||
264 | } | ||
265 | ", | ||
266 | " | ||
267 | mod foo { | ||
268 | pub trait Bar { | ||
269 | type Qux; | ||
270 | const Baz: usize = 42; | ||
271 | const Fez: usize; | ||
272 | fn foo(); | ||
273 | fn bar() {} | ||
274 | } | ||
275 | } | ||
276 | |||
277 | struct Foo { | ||
278 | bar: String, | ||
279 | } | ||
280 | |||
281 | impl foo::Bar for Foo { | ||
282 | $0type Qux; | ||
283 | |||
284 | const Baz: usize = 42; | ||
285 | |||
286 | const Fez: usize; | ||
287 | |||
288 | fn foo() { | ||
289 | todo!() | ||
290 | } | ||
291 | } | ||
292 | ", | ||
293 | ) | ||
294 | } | ||
295 | #[test] | ||
296 | fn add_custom_impl_for_unique_input() { | ||
297 | check_assist( | ||
298 | replace_derive_with_manual_impl, | ||
299 | " | ||
300 | #[derive(Debu<|>g)] | ||
301 | struct Foo { | ||
302 | bar: String, | ||
303 | } | ||
304 | ", | ||
305 | " | ||
306 | struct Foo { | ||
307 | bar: String, | ||
308 | } | ||
309 | |||
310 | impl Debug for Foo { | ||
311 | $0 | ||
312 | } | ||
313 | ", | ||
314 | ) | ||
315 | } | ||
316 | |||
317 | #[test] | ||
318 | fn add_custom_impl_for_with_visibility_modifier() { | ||
319 | check_assist( | ||
320 | replace_derive_with_manual_impl, | ||
321 | " | ||
322 | #[derive(Debug<|>)] | ||
323 | pub struct Foo { | ||
324 | bar: String, | ||
325 | } | ||
326 | ", | ||
327 | " | ||
328 | pub struct Foo { | ||
329 | bar: String, | ||
330 | } | ||
331 | |||
332 | impl Debug for Foo { | ||
333 | $0 | ||
334 | } | ||
335 | ", | ||
336 | ) | ||
337 | } | ||
338 | |||
339 | #[test] | ||
340 | fn add_custom_impl_when_multiple_inputs() { | ||
341 | check_assist( | ||
342 | replace_derive_with_manual_impl, | ||
343 | " | ||
344 | #[derive(Display, Debug<|>, Serialize)] | ||
345 | struct Foo {} | ||
346 | ", | ||
347 | " | ||
348 | #[derive(Display, Serialize)] | ||
349 | struct Foo {} | ||
350 | |||
351 | impl Debug for Foo { | ||
352 | $0 | ||
353 | } | ||
354 | ", | ||
355 | ) | ||
356 | } | ||
357 | |||
358 | #[test] | ||
359 | fn test_ignore_derive_macro_without_input() { | ||
360 | check_assist_not_applicable( | ||
361 | replace_derive_with_manual_impl, | ||
362 | " | ||
363 | #[derive(<|>)] | ||
364 | struct Foo {} | ||
365 | ", | ||
366 | ) | ||
367 | } | ||
368 | |||
369 | #[test] | ||
370 | fn test_ignore_if_cursor_on_param() { | ||
371 | check_assist_not_applicable( | ||
372 | replace_derive_with_manual_impl, | ||
373 | " | ||
374 | #[derive<|>(Debug)] | ||
375 | struct Foo {} | ||
376 | ", | ||
377 | ); | ||
378 | |||
379 | check_assist_not_applicable( | ||
380 | replace_derive_with_manual_impl, | ||
381 | " | ||
382 | #[derive(Debug)<|>] | ||
383 | struct Foo {} | ||
384 | ", | ||
385 | ) | ||
386 | } | ||
387 | |||
388 | #[test] | ||
389 | fn test_ignore_if_not_derive() { | ||
390 | check_assist_not_applicable( | ||
391 | replace_derive_with_manual_impl, | ||
392 | " | ||
393 | #[allow(non_camel_<|>case_types)] | ||
394 | struct Foo {} | ||
395 | ", | ||
396 | ) | ||
397 | } | ||
398 | } | ||