aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/assists
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-11-15 07:01:19 +0000
committerGitHub <[email protected]>2019-11-15 07:01:19 +0000
commit3ad11973ac7596323f085a9fcb9530e47f021c41 (patch)
tree15d397d8732ddd5511f85f45689fb5b32441e36f /crates/ra_assists/src/assists
parenta6d080608db1bcc2a2f8fc586d8a5d3c85e50763 (diff)
parentcbc6f94573d7f4601b739e001de5d5f71ec9b552 (diff)
Merge #2165
2165: ra_assists: Add add_new assist r=matklad a=rep-nop Adds a new assist to autogenerate a new fn based on the selected struct, excluding tuple structs and unions. The fn will inherit the same visibility as the struct and the assist will attempt to reuse any existing impl blocks that exist at the same level of struct. Not marking this as closing #1644 since there's a part 2 of adding autocompletion for when someone starts typing `[pub ]fn new(...` Co-authored-by: Wesley Norris <[email protected]>
Diffstat (limited to 'crates/ra_assists/src/assists')
-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}