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