]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/generate_new.rs
Merge #9260
[rust.git] / crates / ide_assists / src / handlers / generate_new.rs
1 use itertools::Itertools;
2 use stdx::format_to;
3 use syntax::ast::{self, AstNode, NameOwner, StructKind, VisibilityOwner};
4
5 use crate::{
6     utils::{find_impl_block_start, find_struct_impl, generate_impl_text},
7     AssistContext, AssistId, AssistKind, Assists,
8 };
9
10 // Assist: generate_new
11 //
12 // Adds a new inherent impl for a type.
13 //
14 // ```
15 // struct Ctx<T: Clone> {
16 //      data: T,$0
17 // }
18 // ```
19 // ->
20 // ```
21 // struct Ctx<T: Clone> {
22 //      data: T,
23 // }
24 //
25 // impl<T: Clone> Ctx<T> {
26 //     fn $0new(data: T) -> Self { Self { data } }
27 // }
28 // ```
29 pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30     let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
31
32     // We want to only apply this to non-union structs with named fields
33     let field_list = match strukt.kind() {
34         StructKind::Record(named) => named,
35         _ => return None,
36     };
37
38     // Return early if we've found an existing new fn
39     let impl_def = find_struct_impl(ctx, &ast::Adt::Struct(strukt.clone()), "new")?;
40
41     let target = strukt.syntax().text_range();
42     acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
43         let mut buf = String::with_capacity(512);
44
45         if impl_def.is_some() {
46             buf.push('\n');
47         }
48
49         let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
50
51         let params = field_list
52             .fields()
53             .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
54             .format(", ");
55         let fields = field_list.fields().filter_map(|f| f.name()).format(", ");
56
57         format_to!(buf, "    {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
58
59         let start_offset = impl_def
60             .and_then(|impl_def| find_impl_block_start(impl_def, &mut buf))
61             .unwrap_or_else(|| {
62                 buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
63                 strukt.syntax().text_range().end()
64             });
65
66         match ctx.config.snippet_cap {
67             None => builder.insert(start_offset, buf),
68             Some(cap) => {
69                 buf = buf.replace("fn new", "fn $0new");
70                 builder.insert_snippet(cap, start_offset, buf);
71             }
72         }
73     })
74 }
75
76 #[cfg(test)]
77 mod tests {
78     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
79
80     use super::*;
81
82     #[test]
83     fn test_generate_new() {
84         check_assist(
85             generate_new,
86             r#"
87 struct Foo {$0}
88 "#,
89             r#"
90 struct Foo {}
91
92 impl Foo {
93     fn $0new() -> Self { Self {  } }
94 }
95 "#,
96         );
97         check_assist(
98             generate_new,
99             r#"
100 struct Foo<T: Clone> {$0}
101 "#,
102             r#"
103 struct Foo<T: Clone> {}
104
105 impl<T: Clone> Foo<T> {
106     fn $0new() -> Self { Self {  } }
107 }
108 "#,
109         );
110         check_assist(
111             generate_new,
112             r#"
113 struct Foo<'a, T: Foo<'a>> {$0}
114 "#,
115             r#"
116 struct Foo<'a, T: Foo<'a>> {}
117
118 impl<'a, T: Foo<'a>> Foo<'a, T> {
119     fn $0new() -> Self { Self {  } }
120 }
121 "#,
122         );
123         check_assist(
124             generate_new,
125             r#"
126 struct Foo { baz: String $0}
127 "#,
128             r#"
129 struct Foo { baz: String }
130
131 impl Foo {
132     fn $0new(baz: String) -> Self { Self { baz } }
133 }
134 "#,
135         );
136         check_assist(
137             generate_new,
138             r#"
139 struct Foo { baz: String, qux: Vec<i32> $0}
140 "#,
141             r#"
142 struct Foo { baz: String, qux: Vec<i32> }
143
144 impl Foo {
145     fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
146 }
147 "#,
148         );
149     }
150
151     #[test]
152     fn check_that_visibility_modifiers_dont_get_brought_in() {
153         check_assist(
154             generate_new,
155             r#"
156 struct Foo { pub baz: String, pub qux: Vec<i32> $0}
157 "#,
158             r#"
159 struct Foo { pub baz: String, pub qux: Vec<i32> }
160
161 impl Foo {
162     fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
163 }
164 "#,
165         );
166     }
167
168     #[test]
169     fn check_it_reuses_existing_impls() {
170         check_assist(
171             generate_new,
172             r#"
173 struct Foo {$0}
174
175 impl Foo {}
176 "#,
177             r#"
178 struct Foo {}
179
180 impl Foo {
181     fn $0new() -> Self { Self {  } }
182 }
183 "#,
184         );
185         check_assist(
186             generate_new,
187             r#"
188 struct Foo {$0}
189
190 impl Foo {
191     fn qux(&self) {}
192 }
193 "#,
194             r#"
195 struct Foo {}
196
197 impl Foo {
198     fn $0new() -> Self { Self {  } }
199
200     fn qux(&self) {}
201 }
202 "#,
203         );
204
205         check_assist(
206             generate_new,
207             r#"
208 struct Foo {$0}
209
210 impl Foo {
211     fn qux(&self) {}
212     fn baz() -> i32 {
213         5
214     }
215 }
216 "#,
217             r#"
218 struct Foo {}
219
220 impl Foo {
221     fn $0new() -> Self { Self {  } }
222
223     fn qux(&self) {}
224     fn baz() -> i32 {
225         5
226     }
227 }
228 "#,
229         );
230     }
231
232     #[test]
233     fn check_visibility_of_new_fn_based_on_struct() {
234         check_assist(
235             generate_new,
236             r#"
237 pub struct Foo {$0}
238 "#,
239             r#"
240 pub struct Foo {}
241
242 impl Foo {
243     pub fn $0new() -> Self { Self {  } }
244 }
245 "#,
246         );
247         check_assist(
248             generate_new,
249             r#"
250 pub(crate) struct Foo {$0}
251 "#,
252             r#"
253 pub(crate) struct Foo {}
254
255 impl Foo {
256     pub(crate) fn $0new() -> Self { Self {  } }
257 }
258 "#,
259         );
260     }
261
262     #[test]
263     fn generate_new_not_applicable_if_fn_exists() {
264         check_assist_not_applicable(
265             generate_new,
266             r#"
267 struct Foo {$0}
268
269 impl Foo {
270     fn new() -> Self {
271         Self
272     }
273 }
274 "#,
275         );
276
277         check_assist_not_applicable(
278             generate_new,
279             r#"
280 struct Foo {$0}
281
282 impl Foo {
283     fn New() -> Self {
284         Self
285     }
286 }
287 "#,
288         );
289     }
290
291     #[test]
292     fn generate_new_target() {
293         check_assist_target(
294             generate_new,
295             r#"
296 struct SomeThingIrrelevant;
297 /// Has a lifetime parameter
298 struct Foo<'a, T: Foo<'a>> {$0}
299 struct EvenMoreIrrelevant;
300 "#,
301             "/// Has a lifetime parameter
302 struct Foo<'a, T: Foo<'a>> {}",
303         );
304     }
305
306     #[test]
307     fn test_unrelated_new() {
308         check_assist(
309             generate_new,
310             r#"
311 pub struct AstId<N: AstNode> {
312     file_id: HirFileId,
313     file_ast_id: FileAstId<N>,
314 }
315
316 impl<N: AstNode> AstId<N> {
317     pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
318         AstId { file_id, file_ast_id }
319     }
320 }
321
322 pub struct Source<T> {
323     pub file_id: HirFileId,$0
324     pub ast: T,
325 }
326
327 impl<T> Source<T> {
328     pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
329         Source { file_id: self.file_id, ast: f(self.ast) }
330     }
331 }
332 "#,
333             r#"
334 pub struct AstId<N: AstNode> {
335     file_id: HirFileId,
336     file_ast_id: FileAstId<N>,
337 }
338
339 impl<N: AstNode> AstId<N> {
340     pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
341         AstId { file_id, file_ast_id }
342     }
343 }
344
345 pub struct Source<T> {
346     pub file_id: HirFileId,
347     pub ast: T,
348 }
349
350 impl<T> Source<T> {
351     pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
352
353     pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
354         Source { file_id: self.file_id, ast: f(self.ast) }
355     }
356 }
357 "#,
358         );
359     }
360 }