aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/generate_new.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/generate_new.rs')
-rw-r--r--crates/ide_assists/src/handlers/generate_new.rs315
1 files changed, 315 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/generate_new.rs b/crates/ide_assists/src/handlers/generate_new.rs
new file mode 100644
index 000000000..8ce5930b7
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_new.rs
@@ -0,0 +1,315 @@
1use ast::Adt;
2use itertools::Itertools;
3use stdx::format_to;
4use syntax::ast::{self, AstNode, NameOwner, StructKind, VisibilityOwner};
5
6use crate::{
7 utils::{find_impl_block_start, 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// ```
30pub(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_start(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)]
78mod 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
92impl 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
101impl<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
110impl<'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
119impl 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
128impl 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
139impl 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
149impl Foo {}
150",
151"struct Foo {}
152
153impl Foo {
154 fn $0new() -> Self { Self { } }
155}
156",
157 );
158 check_assist(
159 generate_new,
160"struct Foo {$0}
161
162impl Foo {
163 fn qux(&self) {}
164}
165",
166"struct Foo {}
167
168impl 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
180impl Foo {
181 fn qux(&self) {}
182 fn baz() -> i32 {
183 5
184 }
185}
186",
187"struct Foo {}
188
189impl 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
206impl 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
215impl 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 "
226struct Foo {$0}
227
228impl Foo {
229 fn new() -> Self {
230 Self
231 }
232}",
233 );
234
235 check_assist_not_applicable(
236 generate_new,
237 "
238struct Foo {$0}
239
240impl 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 "
253struct SomeThingIrrelevant;
254/// Has a lifetime parameter
255struct Foo<'a, T: Foo<'a>> {$0}
256struct EvenMoreIrrelevant;
257",
258 "/// Has a lifetime parameter
259struct Foo<'a, T: Foo<'a>> {}",
260 );
261 }
262
263 #[test]
264 fn test_unrelated_new() {
265 check_assist(
266 generate_new,
267 r##"
268pub struct AstId<N: AstNode> {
269 file_id: HirFileId,
270 file_ast_id: FileAstId<N>,
271}
272
273impl<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
279pub struct Source<T> {
280 pub file_id: HirFileId,$0
281 pub ast: T,
282}
283
284impl<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##"
290pub struct AstId<N: AstNode> {
291 file_id: HirFileId,
292 file_ast_id: FileAstId<N>,
293}
294
295impl<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
301pub struct Source<T> {
302 pub file_id: HirFileId,
303 pub ast: T,
304}
305
306impl<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}