diff options
author | Chetan Khilosiya <[email protected]> | 2021-02-22 18:47:48 +0000 |
---|---|---|
committer | Chetan Khilosiya <[email protected]> | 2021-02-22 19:29:16 +0000 |
commit | e4756cb4f6e66097638b9d101589358976be2ba8 (patch) | |
tree | b6ca0ae6b45b57834476ae0f9985cec3a6bd9090 /crates/assists/src/handlers/replace_derive_with_manual_impl.rs | |
parent | 8687053b118f47ce1a4962d0baa19b22d40d2758 (diff) |
7526: Rename crate assists to ide_assists.
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 | 404 |
1 files changed, 0 insertions, 404 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 deleted file mode 100644 index c69bc5cac..000000000 --- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs +++ /dev/null | |||
@@ -1,404 +0,0 @@ | |||
1 | use ide_db::helpers::mod_path_to_ast; | ||
2 | use ide_db::imports_locator; | ||
3 | use itertools::Itertools; | ||
4 | use syntax::{ | ||
5 | ast::{self, make, AstNode, NameOwner}, | ||
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, 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 | // ``` | ||
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 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 | |||
94 | fn 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 | |||
151 | fn 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 | |||
172 | fn 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)] | ||
206 | mod 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 | " | ||
216 | mod 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)] | ||
226 | struct Foo { | ||
227 | bar: String, | ||
228 | } | ||
229 | ", | ||
230 | " | ||
231 | mod 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 | |||
240 | struct Foo { | ||
241 | bar: String, | ||
242 | } | ||
243 | |||
244 | impl 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 | " | ||
257 | mod 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)] | ||
268 | struct Foo { | ||
269 | bar: String, | ||
270 | } | ||
271 | ", | ||
272 | " | ||
273 | mod 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 | |||
283 | struct Foo { | ||
284 | bar: String, | ||
285 | } | ||
286 | |||
287 | impl 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)] | ||
307 | struct Foo { | ||
308 | bar: String, | ||
309 | } | ||
310 | ", | ||
311 | " | ||
312 | struct Foo { | ||
313 | bar: String, | ||
314 | } | ||
315 | |||
316 | impl 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)] | ||
329 | pub struct Foo { | ||
330 | bar: String, | ||
331 | } | ||
332 | ", | ||
333 | " | ||
334 | pub struct Foo { | ||
335 | bar: String, | ||
336 | } | ||
337 | |||
338 | impl 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)] | ||
351 | struct Foo {} | ||
352 | ", | ||
353 | " | ||
354 | #[derive(Display, Serialize)] | ||
355 | struct Foo {} | ||
356 | |||
357 | impl 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)] | ||
370 | struct 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)] | ||
381 | struct Foo {} | ||
382 | ", | ||
383 | ); | ||
384 | |||
385 | check_assist_not_applicable( | ||
386 | replace_derive_with_manual_impl, | ||
387 | " | ||
388 | #[derive(Debug)$0] | ||
389 | struct 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)] | ||
400 | struct Foo {} | ||
401 | ", | ||
402 | ) | ||
403 | } | ||
404 | } | ||