diff options
Diffstat (limited to 'crates/ra_assists/src/assists/add_missing_impl_members.rs')
-rw-r--r-- | crates/ra_assists/src/assists/add_missing_impl_members.rs | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/add_missing_impl_members.rs b/crates/ra_assists/src/assists/add_missing_impl_members.rs new file mode 100644 index 000000000..2894bdd8a --- /dev/null +++ b/crates/ra_assists/src/assists/add_missing_impl_members.rs | |||
@@ -0,0 +1,337 @@ | |||
1 | use hir::{db::HirDatabase, HasSource}; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner}, | ||
4 | SmolStr, | ||
5 | }; | ||
6 | |||
7 | use crate::{ast_builder::AstBuilder, ast_editor::AstEditor, Assist, AssistCtx, AssistId}; | ||
8 | |||
9 | #[derive(PartialEq)] | ||
10 | enum AddMissingImplMembersMode { | ||
11 | DefaultMethodsOnly, | ||
12 | NoDefaultMethods, | ||
13 | } | ||
14 | |||
15 | pub(crate) fn add_missing_impl_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
16 | add_missing_impl_members_inner( | ||
17 | ctx, | ||
18 | AddMissingImplMembersMode::NoDefaultMethods, | ||
19 | "add_impl_missing_members", | ||
20 | "add missing impl members", | ||
21 | ) | ||
22 | } | ||
23 | |||
24 | pub(crate) fn add_missing_default_members(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
25 | add_missing_impl_members_inner( | ||
26 | ctx, | ||
27 | AddMissingImplMembersMode::DefaultMethodsOnly, | ||
28 | "add_impl_default_members", | ||
29 | "add impl default members", | ||
30 | ) | ||
31 | } | ||
32 | |||
33 | fn add_missing_impl_members_inner( | ||
34 | mut ctx: AssistCtx<impl HirDatabase>, | ||
35 | mode: AddMissingImplMembersMode, | ||
36 | assist_id: &'static str, | ||
37 | label: &'static str, | ||
38 | ) -> Option<Assist> { | ||
39 | let impl_node = ctx.node_at_offset::<ast::ImplBlock>()?; | ||
40 | let impl_item_list = impl_node.item_list()?; | ||
41 | |||
42 | let trait_def = { | ||
43 | let file_id = ctx.frange.file_id; | ||
44 | let analyzer = hir::SourceAnalyzer::new(ctx.db, file_id, impl_node.syntax(), None); | ||
45 | |||
46 | resolve_target_trait_def(ctx.db, &analyzer, &impl_node)? | ||
47 | }; | ||
48 | |||
49 | let def_name = |item: &ast::ImplItem| -> Option<SmolStr> { | ||
50 | match item { | ||
51 | ast::ImplItem::FnDef(def) => def.name(), | ||
52 | ast::ImplItem::TypeAliasDef(def) => def.name(), | ||
53 | ast::ImplItem::ConstDef(def) => def.name(), | ||
54 | } | ||
55 | .map(|it| it.text().clone()) | ||
56 | }; | ||
57 | |||
58 | let trait_items = trait_def.item_list()?.impl_items(); | ||
59 | let impl_items = impl_item_list.impl_items().collect::<Vec<_>>(); | ||
60 | |||
61 | let missing_items: Vec<_> = trait_items | ||
62 | .filter(|t| def_name(t).is_some()) | ||
63 | .filter(|t| match t { | ||
64 | ast::ImplItem::FnDef(def) => match mode { | ||
65 | AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(), | ||
66 | AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(), | ||
67 | }, | ||
68 | _ => mode == AddMissingImplMembersMode::NoDefaultMethods, | ||
69 | }) | ||
70 | .filter(|t| impl_items.iter().all(|i| def_name(i) != def_name(t))) | ||
71 | .collect(); | ||
72 | if missing_items.is_empty() { | ||
73 | return None; | ||
74 | } | ||
75 | |||
76 | ctx.add_action(AssistId(assist_id), label, |edit| { | ||
77 | let n_existing_items = impl_item_list.impl_items().count(); | ||
78 | let items = missing_items.into_iter().map(|it| match it { | ||
79 | ast::ImplItem::FnDef(def) => strip_docstring(add_body(def).into()), | ||
80 | _ => strip_docstring(it), | ||
81 | }); | ||
82 | let mut ast_editor = AstEditor::new(impl_item_list); | ||
83 | |||
84 | ast_editor.append_items(items); | ||
85 | |||
86 | let first_new_item = ast_editor.ast().impl_items().nth(n_existing_items).unwrap(); | ||
87 | let cursor_position = first_new_item.syntax().text_range().start(); | ||
88 | ast_editor.into_text_edit(edit.text_edit_builder()); | ||
89 | |||
90 | edit.set_cursor(cursor_position); | ||
91 | }); | ||
92 | |||
93 | ctx.build() | ||
94 | } | ||
95 | |||
96 | fn strip_docstring(item: ast::ImplItem) -> ast::ImplItem { | ||
97 | let mut ast_editor = AstEditor::new(item); | ||
98 | ast_editor.strip_attrs_and_docs(); | ||
99 | ast_editor.ast().to_owned() | ||
100 | } | ||
101 | |||
102 | fn add_body(fn_def: ast::FnDef) -> ast::FnDef { | ||
103 | let mut ast_editor = AstEditor::new(fn_def.clone()); | ||
104 | if fn_def.body().is_none() { | ||
105 | ast_editor.set_body(&AstBuilder::<ast::Block>::single_expr( | ||
106 | &AstBuilder::<ast::Expr>::unimplemented(), | ||
107 | )); | ||
108 | } | ||
109 | ast_editor.ast().to_owned() | ||
110 | } | ||
111 | |||
112 | /// Given an `ast::ImplBlock`, resolves the target trait (the one being | ||
113 | /// implemented) to a `ast::TraitDef`. | ||
114 | fn resolve_target_trait_def( | ||
115 | db: &impl HirDatabase, | ||
116 | analyzer: &hir::SourceAnalyzer, | ||
117 | impl_block: &ast::ImplBlock, | ||
118 | ) -> Option<ast::TraitDef> { | ||
119 | let ast_path = impl_block | ||
120 | .target_trait() | ||
121 | .map(|it| it.syntax().clone()) | ||
122 | .and_then(ast::PathType::cast)? | ||
123 | .path()?; | ||
124 | |||
125 | match analyzer.resolve_path(db, &ast_path) { | ||
126 | Some(hir::PathResolution::Def(hir::ModuleDef::Trait(def))) => Some(def.source(db).ast), | ||
127 | _ => None, | ||
128 | } | ||
129 | } | ||
130 | |||
131 | #[cfg(test)] | ||
132 | mod tests { | ||
133 | use super::*; | ||
134 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
135 | |||
136 | #[test] | ||
137 | fn test_add_missing_impl_members() { | ||
138 | check_assist( | ||
139 | add_missing_impl_members, | ||
140 | " | ||
141 | trait Foo { | ||
142 | type Output; | ||
143 | |||
144 | const CONST: usize = 42; | ||
145 | |||
146 | fn foo(&self); | ||
147 | fn bar(&self); | ||
148 | fn baz(&self); | ||
149 | } | ||
150 | |||
151 | struct S; | ||
152 | |||
153 | impl Foo for S { | ||
154 | fn bar(&self) {} | ||
155 | <|> | ||
156 | }", | ||
157 | " | ||
158 | trait Foo { | ||
159 | type Output; | ||
160 | |||
161 | const CONST: usize = 42; | ||
162 | |||
163 | fn foo(&self); | ||
164 | fn bar(&self); | ||
165 | fn baz(&self); | ||
166 | } | ||
167 | |||
168 | struct S; | ||
169 | |||
170 | impl Foo for S { | ||
171 | fn bar(&self) {} | ||
172 | <|>type Output; | ||
173 | const CONST: usize = 42; | ||
174 | fn foo(&self) { unimplemented!() } | ||
175 | fn baz(&self) { unimplemented!() } | ||
176 | |||
177 | }", | ||
178 | ); | ||
179 | } | ||
180 | |||
181 | #[test] | ||
182 | fn test_copied_overriden_members() { | ||
183 | check_assist( | ||
184 | add_missing_impl_members, | ||
185 | " | ||
186 | trait Foo { | ||
187 | fn foo(&self); | ||
188 | fn bar(&self) -> bool { true } | ||
189 | fn baz(&self) -> u32 { 42 } | ||
190 | } | ||
191 | |||
192 | struct S; | ||
193 | |||
194 | impl Foo for S { | ||
195 | fn bar(&self) {} | ||
196 | <|> | ||
197 | }", | ||
198 | " | ||
199 | trait Foo { | ||
200 | fn foo(&self); | ||
201 | fn bar(&self) -> bool { true } | ||
202 | fn baz(&self) -> u32 { 42 } | ||
203 | } | ||
204 | |||
205 | struct S; | ||
206 | |||
207 | impl Foo for S { | ||
208 | fn bar(&self) {} | ||
209 | <|>fn foo(&self) { unimplemented!() } | ||
210 | |||
211 | }", | ||
212 | ); | ||
213 | } | ||
214 | |||
215 | #[test] | ||
216 | fn test_empty_impl_block() { | ||
217 | check_assist( | ||
218 | add_missing_impl_members, | ||
219 | " | ||
220 | trait Foo { fn foo(&self); } | ||
221 | struct S; | ||
222 | impl Foo for S { <|> }", | ||
223 | " | ||
224 | trait Foo { fn foo(&self); } | ||
225 | struct S; | ||
226 | impl Foo for S { | ||
227 | <|>fn foo(&self) { unimplemented!() } | ||
228 | }", | ||
229 | ); | ||
230 | } | ||
231 | |||
232 | #[test] | ||
233 | fn test_cursor_after_empty_impl_block() { | ||
234 | check_assist( | ||
235 | add_missing_impl_members, | ||
236 | " | ||
237 | trait Foo { fn foo(&self); } | ||
238 | struct S; | ||
239 | impl Foo for S {}<|>", | ||
240 | " | ||
241 | trait Foo { fn foo(&self); } | ||
242 | struct S; | ||
243 | impl Foo for S { | ||
244 | <|>fn foo(&self) { unimplemented!() } | ||
245 | }", | ||
246 | ) | ||
247 | } | ||
248 | |||
249 | #[test] | ||
250 | fn test_empty_trait() { | ||
251 | check_assist_not_applicable( | ||
252 | add_missing_impl_members, | ||
253 | " | ||
254 | trait Foo; | ||
255 | struct S; | ||
256 | impl Foo for S { <|> }", | ||
257 | ) | ||
258 | } | ||
259 | |||
260 | #[test] | ||
261 | fn test_ignore_unnamed_trait_members_and_default_methods() { | ||
262 | check_assist_not_applicable( | ||
263 | add_missing_impl_members, | ||
264 | " | ||
265 | trait Foo { | ||
266 | fn (arg: u32); | ||
267 | fn valid(some: u32) -> bool { false } | ||
268 | } | ||
269 | struct S; | ||
270 | impl Foo for S { <|> }", | ||
271 | ) | ||
272 | } | ||
273 | |||
274 | #[test] | ||
275 | fn test_with_docstring_and_attrs() { | ||
276 | check_assist( | ||
277 | add_missing_impl_members, | ||
278 | r#" | ||
279 | #[doc(alias = "test alias")] | ||
280 | trait Foo { | ||
281 | /// doc string | ||
282 | type Output; | ||
283 | |||
284 | #[must_use] | ||
285 | fn foo(&self); | ||
286 | } | ||
287 | struct S; | ||
288 | impl Foo for S {}<|>"#, | ||
289 | r#" | ||
290 | #[doc(alias = "test alias")] | ||
291 | trait Foo { | ||
292 | /// doc string | ||
293 | type Output; | ||
294 | |||
295 | #[must_use] | ||
296 | fn foo(&self); | ||
297 | } | ||
298 | struct S; | ||
299 | impl Foo for S { | ||
300 | <|>type Output; | ||
301 | fn foo(&self) { unimplemented!() } | ||
302 | }"#, | ||
303 | ) | ||
304 | } | ||
305 | |||
306 | #[test] | ||
307 | fn test_default_methods() { | ||
308 | check_assist( | ||
309 | add_missing_default_members, | ||
310 | " | ||
311 | trait Foo { | ||
312 | type Output; | ||
313 | |||
314 | const CONST: usize = 42; | ||
315 | |||
316 | fn valid(some: u32) -> bool { false } | ||
317 | fn foo(some: u32) -> bool; | ||
318 | } | ||
319 | struct S; | ||
320 | impl Foo for S { <|> }", | ||
321 | " | ||
322 | trait Foo { | ||
323 | type Output; | ||
324 | |||
325 | const CONST: usize = 42; | ||
326 | |||
327 | fn valid(some: u32) -> bool { false } | ||
328 | fn foo(some: u32) -> bool; | ||
329 | } | ||
330 | struct S; | ||
331 | impl Foo for S { | ||
332 | <|>fn valid(some: u32) -> bool { false } | ||
333 | }", | ||
334 | ) | ||
335 | } | ||
336 | |||
337 | } | ||