aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
diff options
context:
space:
mode:
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.rs398
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 @@
1use ide_db::imports_locator;
2use itertools::Itertools;
3use syntax::{
4 ast::{self, make, AstNode},
5 Direction, SmolStr,
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, 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// ```
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 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
89fn 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
145fn 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
166fn 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)]
200mod 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 "
210mod 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)]
220struct Foo {
221 bar: String,
222}
223",
224 "
225mod 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
234struct Foo {
235 bar: String,
236}
237
238impl 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 "
251mod 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)]
262struct Foo {
263 bar: String,
264}
265",
266 "
267mod 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
277struct Foo {
278 bar: String,
279}
280
281impl 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)]
301struct Foo {
302 bar: String,
303}
304 ",
305 "
306struct Foo {
307 bar: String,
308}
309
310impl 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<|>)]
323pub struct Foo {
324 bar: String,
325}
326 ",
327 "
328pub struct Foo {
329 bar: String,
330}
331
332impl 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)]
345struct Foo {}
346 ",
347 "
348#[derive(Display, Serialize)]
349struct Foo {}
350
351impl 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(<|>)]
364struct 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)]
375struct Foo {}
376 ",
377 );
378
379 check_assist_not_applicable(
380 replace_derive_with_manual_impl,
381 "
382#[derive(Debug)<|>]
383struct 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)]
394struct Foo {}
395 ",
396 )
397 }
398}