diff options
Diffstat (limited to 'crates/ra_assists/src/assists/add_new.rs')
-rw-r--r-- | crates/ra_assists/src/assists/add_new.rs | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/crates/ra_assists/src/assists/add_new.rs b/crates/ra_assists/src/assists/add_new.rs new file mode 100644 index 000000000..a8839cfba --- /dev/null +++ b/crates/ra_assists/src/assists/add_new.rs | |||
@@ -0,0 +1,379 @@ | |||
1 | use format_buf::format; | ||
2 | use hir::{db::HirDatabase, FromSource}; | ||
3 | use join_to_string::join; | ||
4 | use ra_syntax::{ | ||
5 | ast::{ | ||
6 | self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner, | ||
7 | }, | ||
8 | TextUnit, T, | ||
9 | }; | ||
10 | use std::fmt::Write; | ||
11 | |||
12 | use crate::{Assist, AssistCtx, AssistId}; | ||
13 | |||
14 | // Assist: add_new | ||
15 | // | ||
16 | // Adds a new inherent impl for a type. | ||
17 | // | ||
18 | // ``` | ||
19 | // struct Ctx<T: Clone> { | ||
20 | // data: T,<|> | ||
21 | // } | ||
22 | // ``` | ||
23 | // -> | ||
24 | // ``` | ||
25 | // struct Ctx<T: Clone> { | ||
26 | // data: T, | ||
27 | // } | ||
28 | // | ||
29 | // impl<T: Clone> Ctx<T> { | ||
30 | // fn new(data: T) -> Self { Self { data } } | ||
31 | // } | ||
32 | // | ||
33 | // ``` | ||
34 | pub(crate) fn add_new(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { | ||
35 | let strukt = ctx.find_node_at_offset::<ast::StructDef>()?; | ||
36 | |||
37 | // We want to only apply this to non-union structs with named fields | ||
38 | let field_list = match (strukt.kind(), strukt.is_union()) { | ||
39 | (StructKind::Named(named), false) => named, | ||
40 | _ => return None, | ||
41 | }; | ||
42 | |||
43 | // Return early if we've found an existing new fn | ||
44 | let impl_block = find_struct_impl(&ctx, &strukt)?; | ||
45 | |||
46 | ctx.add_assist(AssistId("add_new"), "add new fn", |edit| { | ||
47 | edit.target(strukt.syntax().text_range()); | ||
48 | |||
49 | let mut buf = String::with_capacity(512); | ||
50 | |||
51 | if impl_block.is_some() { | ||
52 | buf.push('\n'); | ||
53 | } | ||
54 | |||
55 | let vis = strukt.visibility().map(|v| format!("{} ", v.syntax())); | ||
56 | let vis = vis.as_ref().map(String::as_str).unwrap_or(""); | ||
57 | write!(&mut buf, " {}fn new(", vis).unwrap(); | ||
58 | |||
59 | join(field_list.fields().map(|f| { | ||
60 | format!( | ||
61 | "{}: {}", | ||
62 | f.name().unwrap().syntax().text(), | ||
63 | f.ascribed_type().unwrap().syntax().text() | ||
64 | ) | ||
65 | })) | ||
66 | .separator(", ") | ||
67 | .to_buf(&mut buf); | ||
68 | |||
69 | buf.push_str(") -> Self { Self {"); | ||
70 | |||
71 | join(field_list.fields().map(|f| f.name().unwrap().syntax().text())) | ||
72 | .separator(", ") | ||
73 | .surround_with(" ", " ") | ||
74 | .to_buf(&mut buf); | ||
75 | |||
76 | buf.push_str("} }"); | ||
77 | |||
78 | let (start_offset, end_offset) = if let Some(impl_block) = impl_block { | ||
79 | buf.push('\n'); | ||
80 | let start = impl_block | ||
81 | .syntax() | ||
82 | .descendants_with_tokens() | ||
83 | .find(|t| t.kind() == T!['{']) | ||
84 | .unwrap() | ||
85 | .text_range() | ||
86 | .end(); | ||
87 | |||
88 | (start, TextUnit::from_usize(1)) | ||
89 | } else { | ||
90 | buf = generate_impl_text(&strukt, &buf); | ||
91 | let start = strukt.syntax().text_range().end(); | ||
92 | |||
93 | (start, TextUnit::from_usize(3)) | ||
94 | }; | ||
95 | |||
96 | edit.set_cursor(start_offset + TextUnit::of_str(&buf) - end_offset); | ||
97 | edit.insert(start_offset, buf); | ||
98 | }) | ||
99 | } | ||
100 | |||
101 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
102 | // parameters | ||
103 | fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { | ||
104 | let type_params = strukt.type_param_list(); | ||
105 | let mut buf = String::with_capacity(code.len()); | ||
106 | buf.push_str("\n\nimpl"); | ||
107 | if let Some(type_params) = &type_params { | ||
108 | format!(buf, "{}", type_params.syntax()); | ||
109 | } | ||
110 | buf.push_str(" "); | ||
111 | buf.push_str(strukt.name().unwrap().text().as_str()); | ||
112 | if let Some(type_params) = type_params { | ||
113 | let lifetime_params = type_params | ||
114 | .lifetime_params() | ||
115 | .filter_map(|it| it.lifetime_token()) | ||
116 | .map(|it| it.text().clone()); | ||
117 | let type_params = | ||
118 | type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); | ||
119 | join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf); | ||
120 | } | ||
121 | |||
122 | format!(&mut buf, " {{\n{}\n}}\n", code); | ||
123 | |||
124 | buf | ||
125 | } | ||
126 | |||
127 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
128 | // exist within the module/file | ||
129 | // | ||
130 | // Returns `None` if we've found an existing `new` fn | ||
131 | // | ||
132 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
133 | // viable (e.g. we process proc macros, etc) | ||
134 | fn find_struct_impl( | ||
135 | ctx: &AssistCtx<impl HirDatabase>, | ||
136 | strukt: &ast::StructDef, | ||
137 | ) -> Option<Option<ast::ImplBlock>> { | ||
138 | let db = ctx.db; | ||
139 | let module = strukt.syntax().ancestors().find(|node| { | ||
140 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
141 | })?; | ||
142 | |||
143 | let struct_ty = { | ||
144 | let src = hir::Source { file_id: ctx.frange.file_id.into(), ast: strukt.clone() }; | ||
145 | hir::Struct::from_source(db, src).unwrap().ty(db) | ||
146 | }; | ||
147 | |||
148 | let mut found_new_fn = false; | ||
149 | |||
150 | let block = module.descendants().filter_map(ast::ImplBlock::cast).find(|impl_blk| { | ||
151 | if found_new_fn { | ||
152 | return false; | ||
153 | } | ||
154 | |||
155 | let src = hir::Source { file_id: ctx.frange.file_id.into(), ast: impl_blk.clone() }; | ||
156 | let blk = hir::ImplBlock::from_source(db, src).unwrap(); | ||
157 | |||
158 | let same_ty = blk.target_ty(db) == struct_ty; | ||
159 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
160 | |||
161 | found_new_fn = has_new_fn(impl_blk); | ||
162 | |||
163 | same_ty && not_trait_impl | ||
164 | }); | ||
165 | |||
166 | if found_new_fn { | ||
167 | None | ||
168 | } else { | ||
169 | Some(block) | ||
170 | } | ||
171 | } | ||
172 | |||
173 | fn has_new_fn(imp: &ast::ImplBlock) -> bool { | ||
174 | if let Some(il) = imp.item_list() { | ||
175 | for item in il.impl_items() { | ||
176 | if let ast::ImplItem::FnDef(f) = item { | ||
177 | if f.name().unwrap().text().eq_ignore_ascii_case("new") { | ||
178 | return true; | ||
179 | } | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | |||
184 | false | ||
185 | } | ||
186 | |||
187 | #[cfg(test)] | ||
188 | mod tests { | ||
189 | use super::*; | ||
190 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
191 | |||
192 | #[test] | ||
193 | #[rustfmt::skip] | ||
194 | fn test_add_new() { | ||
195 | // Check output of generation | ||
196 | check_assist( | ||
197 | add_new, | ||
198 | "struct Foo {<|>}", | ||
199 | "struct Foo {} | ||
200 | |||
201 | impl Foo { | ||
202 | fn new() -> Self { Self { } }<|> | ||
203 | } | ||
204 | ", | ||
205 | ); | ||
206 | check_assist( | ||
207 | add_new, | ||
208 | "struct Foo<T: Clone> {<|>}", | ||
209 | "struct Foo<T: Clone> {} | ||
210 | |||
211 | impl<T: Clone> Foo<T> { | ||
212 | fn new() -> Self { Self { } }<|> | ||
213 | } | ||
214 | ", | ||
215 | ); | ||
216 | check_assist( | ||
217 | add_new, | ||
218 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
219 | "struct Foo<'a, T: Foo<'a>> {} | ||
220 | |||
221 | impl<'a, T: Foo<'a>> Foo<'a, T> { | ||
222 | fn new() -> Self { Self { } }<|> | ||
223 | } | ||
224 | ", | ||
225 | ); | ||
226 | check_assist( | ||
227 | add_new, | ||
228 | "struct Foo { baz: String <|>}", | ||
229 | "struct Foo { baz: String } | ||
230 | |||
231 | impl Foo { | ||
232 | fn new(baz: String) -> Self { Self { baz } }<|> | ||
233 | } | ||
234 | ", | ||
235 | ); | ||
236 | check_assist( | ||
237 | add_new, | ||
238 | "struct Foo { baz: String, qux: Vec<i32> <|>}", | ||
239 | "struct Foo { baz: String, qux: Vec<i32> } | ||
240 | |||
241 | impl Foo { | ||
242 | fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> | ||
243 | } | ||
244 | ", | ||
245 | ); | ||
246 | |||
247 | // Check that visibility modifiers don't get brought in for fields | ||
248 | check_assist( | ||
249 | add_new, | ||
250 | "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", | ||
251 | "struct Foo { pub baz: String, pub qux: Vec<i32> } | ||
252 | |||
253 | impl Foo { | ||
254 | fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> | ||
255 | } | ||
256 | ", | ||
257 | ); | ||
258 | |||
259 | // Check that it reuses existing impls | ||
260 | check_assist( | ||
261 | add_new, | ||
262 | "struct Foo {<|>} | ||
263 | |||
264 | impl Foo {} | ||
265 | ", | ||
266 | "struct Foo {} | ||
267 | |||
268 | impl Foo { | ||
269 | fn new() -> Self { Self { } }<|> | ||
270 | } | ||
271 | ", | ||
272 | ); | ||
273 | check_assist( | ||
274 | add_new, | ||
275 | "struct Foo {<|>} | ||
276 | |||
277 | impl Foo { | ||
278 | fn qux(&self) {} | ||
279 | } | ||
280 | ", | ||
281 | "struct Foo {} | ||
282 | |||
283 | impl Foo { | ||
284 | fn new() -> Self { Self { } }<|> | ||
285 | |||
286 | fn qux(&self) {} | ||
287 | } | ||
288 | ", | ||
289 | ); | ||
290 | |||
291 | check_assist( | ||
292 | add_new, | ||
293 | "struct Foo {<|>} | ||
294 | |||
295 | impl Foo { | ||
296 | fn qux(&self) {} | ||
297 | fn baz() -> i32 { | ||
298 | 5 | ||
299 | } | ||
300 | } | ||
301 | ", | ||
302 | "struct Foo {} | ||
303 | |||
304 | impl Foo { | ||
305 | fn new() -> Self { Self { } }<|> | ||
306 | |||
307 | fn qux(&self) {} | ||
308 | fn baz() -> i32 { | ||
309 | 5 | ||
310 | } | ||
311 | } | ||
312 | ", | ||
313 | ); | ||
314 | |||
315 | // Check visibility of new fn based on struct | ||
316 | check_assist( | ||
317 | add_new, | ||
318 | "pub struct Foo {<|>}", | ||
319 | "pub struct Foo {} | ||
320 | |||
321 | impl Foo { | ||
322 | pub fn new() -> Self { Self { } }<|> | ||
323 | } | ||
324 | ", | ||
325 | ); | ||
326 | check_assist( | ||
327 | add_new, | ||
328 | "pub(crate) struct Foo {<|>}", | ||
329 | "pub(crate) struct Foo {} | ||
330 | |||
331 | impl Foo { | ||
332 | pub(crate) fn new() -> Self { Self { } }<|> | ||
333 | } | ||
334 | ", | ||
335 | ); | ||
336 | } | ||
337 | |||
338 | #[test] | ||
339 | fn add_new_not_applicable_if_fn_exists() { | ||
340 | check_assist_not_applicable( | ||
341 | add_new, | ||
342 | " | ||
343 | struct Foo {<|>} | ||
344 | |||
345 | impl Foo { | ||
346 | fn new() -> Self { | ||
347 | Self | ||
348 | } | ||
349 | }", | ||
350 | ); | ||
351 | |||
352 | check_assist_not_applicable( | ||
353 | add_new, | ||
354 | " | ||
355 | struct Foo {<|>} | ||
356 | |||
357 | impl Foo { | ||
358 | fn New() -> Self { | ||
359 | Self | ||
360 | } | ||
361 | }", | ||
362 | ); | ||
363 | } | ||
364 | |||
365 | #[test] | ||
366 | fn add_new_target() { | ||
367 | check_assist_target( | ||
368 | add_new, | ||
369 | " | ||
370 | struct SomeThingIrrelevant; | ||
371 | /// Has a lifetime parameter | ||
372 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
373 | struct EvenMoreIrrelevant; | ||
374 | ", | ||
375 | "/// Has a lifetime parameter | ||
376 | struct Foo<'a, T: Foo<'a>> {}", | ||
377 | ); | ||
378 | } | ||
379 | } | ||