diff options
author | Aleksey Kladov <[email protected]> | 2020-08-13 16:33:38 +0100 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2020-08-13 16:33:38 +0100 |
commit | fc34403018079ea053f26d0a31b7517053c7dd8c (patch) | |
tree | 500d7c2ec2179309be12a063634cb6a77c9af845 /crates/ra_assists/src/handlers/generate_new.rs | |
parent | ae3abd6e575940eb1221acf26c09e96352f052fa (diff) |
Rename ra_assists -> assists
Diffstat (limited to 'crates/ra_assists/src/handlers/generate_new.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/generate_new.rs | 421 |
1 files changed, 0 insertions, 421 deletions
diff --git a/crates/ra_assists/src/handlers/generate_new.rs b/crates/ra_assists/src/handlers/generate_new.rs deleted file mode 100644 index 7db10f276..000000000 --- a/crates/ra_assists/src/handlers/generate_new.rs +++ /dev/null | |||
@@ -1,421 +0,0 @@ | |||
1 | use hir::Adt; | ||
2 | use itertools::Itertools; | ||
3 | use stdx::format_to; | ||
4 | use syntax::{ | ||
5 | ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner}, | ||
6 | T, | ||
7 | }; | ||
8 | |||
9 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | ||
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,<|> | ||
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 | // ``` | ||
31 | pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
32 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
33 | |||
34 | // We want to only apply this to non-union structs with named fields | ||
35 | let field_list = match strukt.kind() { | ||
36 | StructKind::Record(named) => named, | ||
37 | _ => return None, | ||
38 | }; | ||
39 | |||
40 | // Return early if we've found an existing new fn | ||
41 | let impl_def = find_struct_impl(&ctx, &strukt)?; | ||
42 | |||
43 | let target = strukt.syntax().text_range(); | ||
44 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { | ||
45 | let mut buf = String::with_capacity(512); | ||
46 | |||
47 | if impl_def.is_some() { | ||
48 | buf.push('\n'); | ||
49 | } | ||
50 | |||
51 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
52 | |||
53 | let params = field_list | ||
54 | .fields() | ||
55 | .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax()))) | ||
56 | .format(", "); | ||
57 | let fields = field_list.fields().filter_map(|f| f.name()).format(", "); | ||
58 | |||
59 | format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields); | ||
60 | |||
61 | let start_offset = impl_def | ||
62 | .and_then(|impl_def| { | ||
63 | buf.push('\n'); | ||
64 | let start = impl_def | ||
65 | .syntax() | ||
66 | .descendants_with_tokens() | ||
67 | .find(|t| t.kind() == T!['{'])? | ||
68 | .text_range() | ||
69 | .end(); | ||
70 | |||
71 | Some(start) | ||
72 | }) | ||
73 | .unwrap_or_else(|| { | ||
74 | buf = generate_impl_text(&strukt, &buf); | ||
75 | strukt.syntax().text_range().end() | ||
76 | }); | ||
77 | |||
78 | match ctx.config.snippet_cap { | ||
79 | None => builder.insert(start_offset, buf), | ||
80 | Some(cap) => { | ||
81 | buf = buf.replace("fn new", "fn $0new"); | ||
82 | builder.insert_snippet(cap, start_offset, buf); | ||
83 | } | ||
84 | } | ||
85 | }) | ||
86 | } | ||
87 | |||
88 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
89 | // parameters | ||
90 | fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String { | ||
91 | let type_params = strukt.generic_param_list(); | ||
92 | let mut buf = String::with_capacity(code.len()); | ||
93 | buf.push_str("\n\nimpl"); | ||
94 | if let Some(type_params) = &type_params { | ||
95 | format_to!(buf, "{}", type_params.syntax()); | ||
96 | } | ||
97 | buf.push_str(" "); | ||
98 | buf.push_str(strukt.name().unwrap().text().as_str()); | ||
99 | if let Some(type_params) = type_params { | ||
100 | let lifetime_params = type_params | ||
101 | .lifetime_params() | ||
102 | .filter_map(|it| it.lifetime_token()) | ||
103 | .map(|it| it.text().clone()); | ||
104 | let type_params = | ||
105 | type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone()); | ||
106 | format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", ")) | ||
107 | } | ||
108 | |||
109 | format_to!(buf, " {{\n{}\n}}\n", code); | ||
110 | |||
111 | buf | ||
112 | } | ||
113 | |||
114 | // Uses a syntax-driven approach to find any impl blocks for the struct that | ||
115 | // exist within the module/file | ||
116 | // | ||
117 | // Returns `None` if we've found an existing `new` fn | ||
118 | // | ||
119 | // FIXME: change the new fn checking to a more semantic approach when that's more | ||
120 | // viable (e.g. we process proc macros, etc) | ||
121 | fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> { | ||
122 | let db = ctx.db(); | ||
123 | let module = strukt.syntax().ancestors().find(|node| { | ||
124 | ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind()) | ||
125 | })?; | ||
126 | |||
127 | let struct_def = ctx.sema.to_def(strukt)?; | ||
128 | |||
129 | let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| { | ||
130 | let blk = ctx.sema.to_def(&impl_blk)?; | ||
131 | |||
132 | // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}` | ||
133 | // (we currently use the wrong type parameter) | ||
134 | // also we wouldn't want to use e.g. `impl S<u32>` | ||
135 | let same_ty = match blk.target_ty(db).as_adt() { | ||
136 | Some(def) => def == Adt::Struct(struct_def), | ||
137 | None => false, | ||
138 | }; | ||
139 | let not_trait_impl = blk.target_trait(db).is_none(); | ||
140 | |||
141 | if !(same_ty && not_trait_impl) { | ||
142 | None | ||
143 | } else { | ||
144 | Some(impl_blk) | ||
145 | } | ||
146 | }); | ||
147 | |||
148 | if let Some(ref impl_blk) = block { | ||
149 | if has_new_fn(impl_blk) { | ||
150 | return None; | ||
151 | } | ||
152 | } | ||
153 | |||
154 | Some(block) | ||
155 | } | ||
156 | |||
157 | fn has_new_fn(imp: &ast::Impl) -> bool { | ||
158 | if let Some(il) = imp.assoc_item_list() { | ||
159 | for item in il.assoc_items() { | ||
160 | if let ast::AssocItem::Fn(f) = item { | ||
161 | if let Some(name) = f.name() { | ||
162 | if name.text().eq_ignore_ascii_case("new") { | ||
163 | return true; | ||
164 | } | ||
165 | } | ||
166 | } | ||
167 | } | ||
168 | } | ||
169 | |||
170 | false | ||
171 | } | ||
172 | |||
173 | #[cfg(test)] | ||
174 | mod tests { | ||
175 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
176 | |||
177 | use super::*; | ||
178 | |||
179 | #[test] | ||
180 | #[rustfmt::skip] | ||
181 | fn test_generate_new() { | ||
182 | // Check output of generation | ||
183 | check_assist( | ||
184 | generate_new, | ||
185 | "struct Foo {<|>}", | ||
186 | "struct Foo {} | ||
187 | |||
188 | impl Foo { | ||
189 | fn $0new() -> Self { Self { } } | ||
190 | } | ||
191 | ", | ||
192 | ); | ||
193 | check_assist( | ||
194 | generate_new, | ||
195 | "struct Foo<T: Clone> {<|>}", | ||
196 | "struct Foo<T: Clone> {} | ||
197 | |||
198 | impl<T: Clone> Foo<T> { | ||
199 | fn $0new() -> Self { Self { } } | ||
200 | } | ||
201 | ", | ||
202 | ); | ||
203 | check_assist( | ||
204 | generate_new, | ||
205 | "struct Foo<'a, T: Foo<'a>> {<|>}", | ||
206 | "struct Foo<'a, T: Foo<'a>> {} | ||
207 | |||
208 | impl<'a, T: Foo<'a>> Foo<'a, T> { | ||
209 | fn $0new() -> Self { Self { } } | ||
210 | } | ||
211 | ", | ||
212 | ); | ||
213 | check_assist( | ||
214 | generate_new, | ||
215 | "struct Foo { baz: String <|>}", | ||
216 | "struct Foo { baz: String } | ||
217 | |||
218 | impl Foo { | ||
219 | fn $0new(baz: String) -> Self { Self { baz } } | ||
220 | } | ||
221 | ", | ||
222 | ); | ||
223 | check_assist( | ||
224 | generate_new, | ||
225 | "struct Foo { baz: String, qux: Vec<i32> <|>}", | ||
226 | "struct Foo { baz: String, qux: Vec<i32> } | ||
227 | |||
228 | impl Foo { | ||
229 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } | ||
230 | } | ||
231 | ", | ||
232 | ); | ||
233 | |||
234 | // Check that visibility modifiers don't get brought in for fields | ||
235 | check_assist( | ||
236 | generate_new, | ||
237 | "struct Foo { pub baz: String, pub qux: Vec<i32> <|>}", | ||
238 | "struct Foo { pub baz: String, pub qux: Vec<i32> } | ||
239 | |||
240 | impl Foo { | ||
241 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } | ||
242 | } | ||
243 | ", | ||
244 | ); | ||
245 | |||
246 | // Check that it reuses existing impls | ||
247 | check_assist( | ||
248 | generate_new, | ||
249 | "struct Foo {<|>} | ||
250 | |||
251 | impl Foo {} | ||
252 | ", | ||
253 | "struct Foo {} | ||
254 | |||
255 | impl Foo { | ||
256 | fn $0new() -> Self { Self { } } | ||
257 | } | ||
258 | ", | ||
259 | ); | ||
260 | check_assist( | ||
261 | generate_new, | ||
262 | "struct Foo {<|>} | ||
263 | |||
264 | impl Foo { | ||
265 | fn qux(&self) {} | ||
266 | } | ||
267 | ", | ||
268 | "struct Foo {} | ||
269 | |||
270 | impl Foo { | ||
271 | fn $0new() -> Self { Self { } } | ||
272 | |||
273 | fn qux(&self) {} | ||
274 | } | ||
275 | ", | ||
276 | ); | ||
277 | |||
278 | check_assist( | ||
279 | generate_new, | ||
280 | "struct Foo {<|>} | ||
281 | |||
282 | impl Foo { | ||
283 | fn qux(&self) {} | ||
284 | fn baz() -> i32 { | ||
285 | 5 | ||
286 | } | ||
287 | } | ||
288 | ", | ||
289 | "struct Foo {} | ||
290 | |||
291 | impl Foo { | ||
292 | fn $0new() -> Self { Self { } } | ||
293 | |||
294 | fn qux(&self) {} | ||
295 | fn baz() -> i32 { | ||
296 | 5 | ||
297 | } | ||
298 | } | ||
299 | ", | ||
300 | ); | ||
301 | |||
302 | // Check visibility of new fn based on struct | ||
303 | check_assist( | ||
304 | generate_new, | ||
305 | "pub struct Foo {<|>}", | ||
306 | "pub struct Foo {} | ||
307 | |||
308 | impl Foo { | ||
309 | pub fn $0new() -> Self { Self { } } | ||
310 | } | ||
311 | ", | ||
312 | ); | ||
313 | check_assist( | ||
314 | generate_new, | ||
315 | "pub(crate) struct Foo {<|>}", | ||
316 | "pub(crate) struct Foo {} | ||
317 | |||
318 | impl Foo { | ||
319 | pub(crate) fn $0new() -> Self { Self { } } | ||
320 | } | ||
321 | ", | ||
322 | ); | ||
323 | } | ||
324 | |||
325 | #[test] | ||
326 | fn generate_new_not_applicable_if_fn_exists() { | ||
327 | check_assist_not_applicable( | ||
328 | generate_new, | ||
329 | " | ||
330 | struct Foo {<|>} | ||
331 | |||
332 | impl Foo { | ||
333 | fn new() -> Self { | ||
334 | Self | ||
335 | } | ||
336 | }", | ||
337 | ); | ||
338 | |||
339 | check_assist_not_applicable( | ||
340 | generate_new, | ||
341 | " | ||
342 | struct Foo {<|>} | ||
343 | |||
344 | impl Foo { | ||
345 | fn New() -> Self { | ||
346 | Self | ||
347 | } | ||
348 | }", | ||
349 | ); | ||
350 | } | ||
351 | |||
352 | #[test] | ||
353 | fn generate_new_target() { | ||
354 | check_assist_target( | ||
355 | generate_new, | ||
356 | " | ||
357 | struct SomeThingIrrelevant; | ||
358 | /// Has a lifetime parameter | ||
359 | struct Foo<'a, T: Foo<'a>> {<|>} | ||
360 | struct EvenMoreIrrelevant; | ||
361 | ", | ||
362 | "/// Has a lifetime parameter | ||
363 | struct Foo<'a, T: Foo<'a>> {}", | ||
364 | ); | ||
365 | } | ||
366 | |||
367 | #[test] | ||
368 | fn test_unrelated_new() { | ||
369 | check_assist( | ||
370 | generate_new, | ||
371 | r##" | ||
372 | pub struct AstId<N: AstNode> { | ||
373 | file_id: HirFileId, | ||
374 | file_ast_id: FileAstId<N>, | ||
375 | } | ||
376 | |||
377 | impl<N: AstNode> AstId<N> { | ||
378 | pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { | ||
379 | AstId { file_id, file_ast_id } | ||
380 | } | ||
381 | } | ||
382 | |||
383 | pub struct Source<T> { | ||
384 | pub file_id: HirFileId,<|> | ||
385 | pub ast: T, | ||
386 | } | ||
387 | |||
388 | impl<T> Source<T> { | ||
389 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | ||
390 | Source { file_id: self.file_id, ast: f(self.ast) } | ||
391 | } | ||
392 | } | ||
393 | "##, | ||
394 | r##" | ||
395 | pub struct AstId<N: AstNode> { | ||
396 | file_id: HirFileId, | ||
397 | file_ast_id: FileAstId<N>, | ||
398 | } | ||
399 | |||
400 | impl<N: AstNode> AstId<N> { | ||
401 | pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> { | ||
402 | AstId { file_id, file_ast_id } | ||
403 | } | ||
404 | } | ||
405 | |||
406 | pub struct Source<T> { | ||
407 | pub file_id: HirFileId, | ||
408 | pub ast: T, | ||
409 | } | ||
410 | |||
411 | impl<T> Source<T> { | ||
412 | pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } } | ||
413 | |||
414 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | ||
415 | Source { file_id: self.file_id, ast: f(self.ast) } | ||
416 | } | ||
417 | } | ||
418 | "##, | ||
419 | ); | ||
420 | } | ||
421 | } | ||