aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assists/add_new.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/assists/add_new.rs')
-rw-r--r--crates/ra_assists/src/assists/add_new.rs379
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 @@
1use format_buf::format;
2use hir::{db::HirDatabase, FromSource};
3use join_to_string::join;
4use ra_syntax::{
5 ast::{
6 self, AstNode, NameOwner, StructKind, TypeAscriptionOwner, TypeParamsOwner, VisibilityOwner,
7 },
8 TextUnit, T,
9};
10use std::fmt::Write;
11
12use 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// ```
34pub(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
103fn 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)
134fn 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
173fn 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)]
188mod 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
201impl 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
211impl<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
221impl<'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
231impl 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
241impl 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
253impl 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
264impl Foo {}
265",
266"struct Foo {}
267
268impl Foo {
269 fn new() -> Self { Self { } }<|>
270}
271",
272 );
273 check_assist(
274 add_new,
275"struct Foo {<|>}
276
277impl Foo {
278 fn qux(&self) {}
279}
280",
281"struct Foo {}
282
283impl 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
295impl Foo {
296 fn qux(&self) {}
297 fn baz() -> i32 {
298 5
299 }
300}
301",
302"struct Foo {}
303
304impl 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
321impl 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
331impl 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 "
343struct Foo {<|>}
344
345impl Foo {
346 fn new() -> Self {
347 Self
348 }
349}",
350 );
351
352 check_assist_not_applicable(
353 add_new,
354 "
355struct Foo {<|>}
356
357impl 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 "
370struct SomeThingIrrelevant;
371/// Has a lifetime parameter
372struct Foo<'a, T: Foo<'a>> {<|>}
373struct EvenMoreIrrelevant;
374",
375 "/// Has a lifetime parameter
376struct Foo<'a, T: Foo<'a>> {}",
377 );
378 }
379}