diff options
Diffstat (limited to 'crates/ra_assists/src/handlers/add_new.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/add_new.rs | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/add_new.rs b/crates/ra_assists/src/handlers/add_new.rs new file mode 100644 index 000000000..a08639311 --- /dev/null +++ b/crates/ra_assists/src/handlers/add_new.rs | |||
@@ -0,0 +1,430 @@ | |||
1 | use format_buf::format; | ||
2 | use hir::InFile; | ||
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) -> 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() { | ||
39 | StructKind::Record(named) => 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 default constructor", |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().filter_map(|f| { | ||
60 | Some(format!("{}: {}", f.name()?.syntax().text(), f.ascribed_type()?.syntax().text())) | ||
61 | })) | ||
62 | .separator(", ") | ||
63 | .to_buf(&mut buf); | ||
64 | |||
65 | buf.push_str(") -> Self { Self {"); | ||
66 | |||
67 | join(field_list.fields().filter_map(|f| Some(f.name()?.syntax().text()))) | ||
68 | .separator(", ") | ||
69 | .surround_with(" ", " ") | ||
70 | .to_buf(&mut buf); | ||
71 | |||
72 | buf.push_str("} }"); | ||
73 | |||
74 | let (start_offset, end_offset) = impl_block | ||
75 | .and_then(|impl_block| { | ||
76 | buf.push('\n'); | ||
77 | let start = impl_block | ||
78 | .syntax() | ||
79 | .descendants_with_tokens() | ||
80 | .find(|t| t.kind() == T!['{'])? | ||
81 | .text_range() | ||
82 | .end(); | ||
83 | |||
84 | Some((start, TextUnit::from_usize(1))) | ||
85 | }) | ||
86 | .unwrap_or_else(|| { | ||
87 | buf = generate_impl_text(&strukt, &buf); | ||
88 | let start = strukt.syntax().text_range().end(); | ||
89 | |||
90 | (start, TextUnit::from_usize(3)) | ||
91 | }); | ||
92 | |||
93 | edit.set_cursor(start_offset + TextUnit::of_str(&buf) - end_offset); | ||
94 | edit.insert(start_offset, buf); | ||
95 | }) | ||
96 | } | ||
97 | |||
98 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
99 | // parameters | ||
100 | fn generate_impl_text(strukt: &ast::StructDef, code: &str) -> String { | ||
101 | let type_params = strukt.type_param_list(); | ||
102 | let mut buf = String::with_capacity(code.len()); | ||
103 | buf.push_str("\n\nimpl"); | ||
104 | if let Some(type_params) = &type_params { | ||
105 | format!(buf, "{}", type_params.syntax()); | ||
106 | } | ||
107 | buf.push_str(" "); | ||
108 | buf.push_str(strukt.name().unwrap().text().as_str()); | ||
109 | if let Some(type_params) = type_params { | ||
110 | let lifetime_params = type_params | ||
111 | .lifetime_params() | ||
112 | .filter_map(|it| it.lifetime_token()) | ||
113 | .map(|it| it.text().clone()); | ||
114 | let type_params = | ||
115 | type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); | ||
116 | join(lifetime_params.chain(type_params)).surround_with("<", ">").to_buf(&mut buf); | ||
117 | } | ||
118 | |||
119 | format!(&mut buf, " {{\n{}\n}}\n", code); | ||
120 | |||
121 | buf | ||
122 | } | ||
123 | |||
124 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
125 | // exist within the module/file | ||
126 | // | ||
127 | // Returns `None` if we've found an existing `new` fn | ||
128 | // | ||
129 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
130 | // viable (e.g. we process proc macros, etc) | ||
131 | fn find_struct_impl(ctx: &AssistCtx, strukt: &ast::StructDef) -> Option<Option<ast::ImplBlock>> { | ||
132 | let db = ctx.db; | ||
133 | let module = strukt.syntax().ancestors().find(|node| { | ||
134 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
135 | })?; | ||
136 | let mut sb = ctx.source_binder(); | ||
137 | |||
138 | let struct_ty = { | ||
139 | let src = InFile { file_id: ctx.frange.file_id.into(), value: strukt.clone() }; | ||
140 | sb.to_def(src)?.ty(db) | ||
141 | }; | ||
142 | |||
143 | let block = module.descendants().filter_map(ast::ImplBlock::cast).find_map(|impl_blk| { | ||
144 | let src = InFile { file_id: ctx.frange.file_id.into(), value: impl_blk.clone() }; | ||
145 | let blk = sb.to_def(src)?; | ||
146 | |||
147 | let same_ty = blk.target_ty(db) == struct_ty; | ||
148 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
149 | |||
150 | if !(same_ty && not_trait_impl) { | ||
151 | None | ||
152 | } else { | ||
153 | Some(impl_blk) | ||
154 | } | ||
155 | }); | ||
156 | |||
157 | if let Some(ref impl_blk) = block { | ||
158 | if has_new_fn(impl_blk) { | ||
159 | return None; | ||
160 | } | ||
161 | } | ||
162 | |||
163 | Some(block) | ||
164 | } | ||
165 | |||
166 | fn has_new_fn(imp: &ast::ImplBlock) -> bool { | ||
167 | if let Some(il) = imp.item_list() { | ||
168 | for item in il.impl_items() { | ||
169 | if let ast::ImplItem::FnDef(f) = item { | ||
170 | if let Some(name) = f.name() { | ||
171 | if name.text().eq_ignore_ascii_case("new") { | ||
172 | return true; | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | |||
179 | false | ||
180 | } | ||
181 | |||
182 | #[cfg(test)] | ||
183 | mod tests { | ||
184 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
185 | |||
186 | use super::*; | ||
187 | |||
188 | #[test] | ||
189 | #[rustfmt::skip] | ||
190 | fn test_add_new() { | ||
191 | // Check output of generation | ||
192 | check_assist( | ||
193 | add_new, | ||
194 | "struct Foo {<|>}", | ||
195 | "struct Foo {} | ||
196 | |||
197 | impl Foo { | ||
198 | fn new() -> Self { Self { } }<|> | ||
199 | } | ||
200 | ", | ||
201 | ); | ||
202 | check_assist( | ||
203 | add_new, | ||
204 | "struct Foo<T: Clone> {<|>}", | ||
205 | "struct Foo<T: Clone> {} | ||
206 | |||
207 | impl<T: Clone> Foo<T> { | ||
208 | fn new() -> Self { Self { } }<|> | ||
209 | } | ||
210 | ", | ||
211 | ); | ||
212 | check_assist( | ||
213 | add_new, | ||
214 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
215 | "struct Foo<'a, T: Foo<'a>> {} | ||
216 | |||
217 | impl<'a, T: Foo<'a>> Foo<'a, T> { | ||
218 | fn new() -> Self { Self { } }<|> | ||
219 | } | ||
220 | ", | ||
221 | ); | ||
222 | check_assist( | ||
223 | add_new, | ||
224 | "struct Foo { baz: String <|>}", | ||
225 | "struct Foo { baz: String } | ||
226 | |||
227 | impl Foo { | ||
228 | fn new(baz: String) -> Self { Self { baz } }<|> | ||
229 | } | ||
230 | ", | ||
231 | ); | ||
232 | check_assist( | ||
233 | add_new, | ||
234 | "struct Foo { baz: String, qux: Vec<i32> <|>}", | ||
235 | "struct Foo { baz: String, qux: Vec<i32> } | ||
236 | |||
237 | impl Foo { | ||
238 | fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> | ||
239 | } | ||
240 | ", | ||
241 | ); | ||
242 | |||
243 | // Check that visibility modifiers don't get brought in for fields | ||
244 | check_assist( | ||
245 | add_new, | ||
246 | "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", | ||
247 | "struct Foo { pub baz: String, pub qux: Vec<i32> } | ||
248 | |||
249 | impl Foo { | ||
250 | fn new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }<|> | ||
251 | } | ||
252 | ", | ||
253 | ); | ||
254 | |||
255 | // Check that it reuses existing impls | ||
256 | check_assist( | ||
257 | add_new, | ||
258 | "struct Foo {<|>} | ||
259 | |||
260 | impl Foo {} | ||
261 | ", | ||
262 | "struct Foo {} | ||
263 | |||
264 | impl Foo { | ||
265 | fn new() -> Self { Self { } }<|> | ||
266 | } | ||
267 | ", | ||
268 | ); | ||
269 | check_assist( | ||
270 | add_new, | ||
271 | "struct Foo {<|>} | ||
272 | |||
273 | impl Foo { | ||
274 | fn qux(&self) {} | ||
275 | } | ||
276 | ", | ||
277 | "struct Foo {} | ||
278 | |||
279 | impl Foo { | ||
280 | fn new() -> Self { Self { } }<|> | ||
281 | |||
282 | fn qux(&self) {} | ||
283 | } | ||
284 | ", | ||
285 | ); | ||
286 | |||
287 | check_assist( | ||
288 | add_new, | ||
289 | "struct Foo {<|>} | ||
290 | |||
291 | impl Foo { | ||
292 | fn qux(&self) {} | ||
293 | fn baz() -> i32 { | ||
294 | 5 | ||
295 | } | ||
296 | } | ||
297 | ", | ||
298 | "struct Foo {} | ||
299 | |||
300 | impl Foo { | ||
301 | fn new() -> Self { Self { } }<|> | ||
302 | |||
303 | fn qux(&self) {} | ||
304 | fn baz() -> i32 { | ||
305 | 5 | ||
306 | } | ||
307 | } | ||
308 | ", | ||
309 | ); | ||
310 | |||
311 | // Check visibility of new fn based on struct | ||
312 | check_assist( | ||
313 | add_new, | ||
314 | "pub struct Foo {<|>}", | ||
315 | "pub struct Foo {} | ||
316 | |||
317 | impl Foo { | ||
318 | pub fn new() -> Self { Self { } }<|> | ||
319 | } | ||
320 | ", | ||
321 | ); | ||
322 | check_assist( | ||
323 | add_new, | ||
324 | "pub(crate) struct Foo {<|>}", | ||
325 | "pub(crate) struct Foo {} | ||
326 | |||
327 | impl Foo { | ||
328 | pub(crate) fn new() -> Self { Self { } }<|> | ||
329 | } | ||
330 | ", | ||
331 | ); | ||
332 | } | ||
333 | |||
334 | #[test] | ||
335 | fn add_new_not_applicable_if_fn_exists() { | ||
336 | check_assist_not_applicable( | ||
337 | add_new, | ||
338 | " | ||
339 | struct Foo {<|>} | ||
340 | |||
341 | impl Foo { | ||
342 | fn new() -> Self { | ||
343 | Self | ||
344 | } | ||
345 | }", | ||
346 | ); | ||
347 | |||
348 | check_assist_not_applicable( | ||
349 | add_new, | ||
350 | " | ||
351 | struct Foo {<|>} | ||
352 | |||
353 | impl Foo { | ||
354 | fn New() -> Self { | ||
355 | Self | ||
356 | } | ||
357 | }", | ||
358 | ); | ||
359 | } | ||
360 | |||
361 | #[test] | ||
362 | fn add_new_target() { | ||
363 | check_assist_target( | ||
364 | add_new, | ||
365 | " | ||
366 | struct SomeThingIrrelevant; | ||
367 | /// Has a lifetime parameter | ||
368 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
369 | struct EvenMoreIrrelevant; | ||
370 | ", | ||
371 | "/// Has a lifetime parameter | ||
372 | struct Foo<'a, T: Foo<'a>> {}", | ||
373 | ); | ||
374 | } | ||
375 | |||
376 | #[test] | ||
377 | fn test_unrelated_new() { | ||
378 | check_assist( | ||
379 | add_new, | ||
380 | r##" | ||
381 | pub struct AstId<N: AstNode> { | ||
382 | file_id: HirFileId, | ||
383 | file_ast_id: FileAstId<N>, | ||
384 | } | ||
385 | |||
386 | impl<N: AstNode> AstId<N> { | ||
387 | pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { | ||
388 | AstId { file_id, file_ast_id } | ||
389 | } | ||
390 | } | ||
391 | |||
392 | pub struct Source<T> { | ||
393 | pub file_id: HirFileId,<|> | ||
394 | pub ast: T, | ||
395 | } | ||
396 | |||
397 | impl<T> Source<T> { | ||
398 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | ||
399 | Source { file_id: self.file_id, ast: f(self.ast) } | ||
400 | } | ||
401 | } | ||
402 | "##, | ||
403 | r##" | ||
404 | pub struct AstId<N: AstNode> { | ||
405 | file_id: HirFileId, | ||
406 | file_ast_id: FileAstId<N>, | ||
407 | } | ||
408 | |||
409 | impl<N: AstNode> AstId<N> { | ||
410 | pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { | ||
411 | AstId { file_id, file_ast_id } | ||
412 | } | ||
413 | } | ||
414 | |||
415 | pub struct Source<T> { | ||
416 | pub file_id: HirFileId, | ||
417 | pub ast: T, | ||
418 | } | ||
419 | |||
420 | impl<T> Source<T> { | ||
421 | pub fn new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }<|> | ||
422 | |||
423 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | ||
424 | Source { file_id: self.file_id, ast: f(self.ast) } | ||
425 | } | ||
426 | } | ||
427 | "##, | ||
428 | ); | ||
429 | } | ||
430 | } | ||