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