diff options
Diffstat (limited to 'crates/ide_assists/src/handlers/generate_getter.rs')
-rw-r--r-- | crates/ide_assists/src/handlers/generate_getter.rs | 292 |
1 files changed, 238 insertions, 54 deletions
diff --git a/crates/ide_assists/src/handlers/generate_getter.rs b/crates/ide_assists/src/handlers/generate_getter.rs index df7d1bb95..09971226e 100644 --- a/crates/ide_assists/src/handlers/generate_getter.rs +++ b/crates/ide_assists/src/handlers/generate_getter.rs | |||
@@ -23,12 +23,46 @@ use crate::{ | |||
23 | // | 23 | // |
24 | // impl Person { | 24 | // impl Person { |
25 | // /// Get a reference to the person's name. | 25 | // /// Get a reference to the person's name. |
26 | // fn name(&self) -> &String { | 26 | // fn $0name(&self) -> &str { |
27 | // &self.name | 27 | // self.name.as_str() |
28 | // } | 28 | // } |
29 | // } | 29 | // } |
30 | // ``` | 30 | // ``` |
31 | pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
32 | generate_getter_impl(acc, ctx, false) | ||
33 | } | ||
34 | |||
35 | // Assist: generate_getter_mut | ||
36 | // | ||
37 | // Generate a mut getter method. | ||
38 | // | ||
39 | // ``` | ||
40 | // struct Person { | ||
41 | // nam$0e: String, | ||
42 | // } | ||
43 | // ``` | ||
44 | // -> | ||
45 | // ``` | ||
46 | // struct Person { | ||
47 | // name: String, | ||
48 | // } | ||
49 | // | ||
50 | // impl Person { | ||
51 | // /// Get a mutable reference to the person's name. | ||
52 | // fn $0name_mut(&mut self) -> &mut String { | ||
53 | // &mut self.name | ||
54 | // } | ||
55 | // } | ||
56 | // ``` | ||
57 | pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
58 | generate_getter_impl(acc, ctx, true) | ||
59 | } | ||
60 | |||
61 | pub(crate) fn generate_getter_impl( | ||
62 | acc: &mut Assists, | ||
63 | ctx: &AssistContext, | ||
64 | mutable: bool, | ||
65 | ) -> Option<()> { | ||
32 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | 66 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; |
33 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; | 67 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; |
34 | 68 | ||
@@ -37,39 +71,52 @@ pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
37 | let field_ty = field.ty()?; | 71 | let field_ty = field.ty()?; |
38 | 72 | ||
39 | // Return early if we've found an existing fn | 73 | // Return early if we've found an existing fn |
40 | let fn_name = to_lower_snake_case(&field_name.to_string()); | 74 | let mut fn_name = to_lower_snake_case(&field_name.to_string()); |
75 | if mutable { | ||
76 | format_to!(fn_name, "_mut"); | ||
77 | } | ||
41 | let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?; | 78 | let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?; |
42 | 79 | ||
80 | let (id, label) = if mutable { | ||
81 | ("generate_getter_mut", "Generate a mut getter method") | ||
82 | } else { | ||
83 | ("generate_getter", "Generate a getter method") | ||
84 | }; | ||
43 | let target = field.syntax().text_range(); | 85 | let target = field.syntax().text_range(); |
44 | acc.add_group( | 86 | acc.add_group( |
45 | &GroupLabel("Generate getter/setter".to_owned()), | 87 | &GroupLabel("Generate getter/setter".to_owned()), |
46 | AssistId("generate_getter", AssistKind::Generate), | 88 | AssistId(id, AssistKind::Generate), |
47 | "Generate a getter method", | 89 | label, |
48 | target, | 90 | target, |
49 | |builder| { | 91 | |builder| { |
50 | let mut buf = String::with_capacity(512); | 92 | let mut buf = String::with_capacity(512); |
51 | 93 | ||
52 | let fn_name_spaced = fn_name.replace('_', " "); | ||
53 | let strukt_name_spaced = | ||
54 | to_lower_snake_case(&strukt_name.to_string()).replace('_', " "); | ||
55 | |||
56 | if impl_def.is_some() { | 94 | if impl_def.is_some() { |
57 | buf.push('\n'); | 95 | buf.push('\n'); |
58 | } | 96 | } |
59 | 97 | ||
60 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); | 98 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); |
99 | let (ty, body) = if mutable { | ||
100 | (format!("&mut {}", field_ty), format!("&mut self.{}", field_name)) | ||
101 | } else { | ||
102 | useless_type_special_case(&field_name.to_string(), &field_ty) | ||
103 | .unwrap_or_else(|| (format!("&{}", field_ty), format!("&self.{}", field_name))) | ||
104 | }; | ||
105 | |||
61 | format_to!( | 106 | format_to!( |
62 | buf, | 107 | buf, |
63 | " /// Get a reference to the {}'s {}. | 108 | " /// Get a {}reference to the {}'s {}. |
64 | {}fn {}(&self) -> &{} {{ | 109 | {}fn {}(&{}self) -> {} {{ |
65 | &self.{} | 110 | {} |
66 | }}", | 111 | }}", |
67 | strukt_name_spaced, | 112 | mutable.then(|| "mutable ").unwrap_or_default(), |
68 | fn_name_spaced, | 113 | to_lower_snake_case(&strukt_name.to_string()).replace('_', " "), |
114 | fn_name.trim_end_matches("_mut").replace('_', " "), | ||
69 | vis, | 115 | vis, |
70 | fn_name, | 116 | fn_name, |
71 | field_ty, | 117 | mutable.then(|| "mut ").unwrap_or_default(), |
72 | fn_name, | 118 | ty, |
119 | body, | ||
73 | ); | 120 | ); |
74 | 121 | ||
75 | let start_offset = impl_def | 122 | let start_offset = impl_def |
@@ -79,56 +126,120 @@ pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option< | |||
79 | strukt.syntax().text_range().end() | 126 | strukt.syntax().text_range().end() |
80 | }); | 127 | }); |
81 | 128 | ||
82 | builder.insert(start_offset, buf); | 129 | match ctx.config.snippet_cap { |
130 | Some(cap) => { | ||
131 | builder.insert_snippet(cap, start_offset, buf.replacen("fn ", "fn $0", 1)) | ||
132 | } | ||
133 | None => builder.insert(start_offset, buf), | ||
134 | } | ||
83 | }, | 135 | }, |
84 | ) | 136 | ) |
85 | } | 137 | } |
86 | 138 | ||
139 | fn useless_type_special_case(field_name: &str, field_ty: &ast::Type) -> Option<(String, String)> { | ||
140 | if field_ty.to_string() == "String" { | ||
141 | cov_mark::hit!(useless_type_special_case); | ||
142 | return Some(("&str".to_string(), format!("self.{}.as_str()", field_name))); | ||
143 | } | ||
144 | if let Some(arg) = ty_ctor(field_ty, "Vec") { | ||
145 | return Some((format!("&[{}]", arg), format!("self.{}.as_slice()", field_name))); | ||
146 | } | ||
147 | if let Some(arg) = ty_ctor(field_ty, "Box") { | ||
148 | return Some((format!("&{}", arg), format!("self.{}.as_ref()", field_name))); | ||
149 | } | ||
150 | if let Some(arg) = ty_ctor(field_ty, "Option") { | ||
151 | return Some((format!("Option<&{}>", arg), format!("self.{}.as_ref()", field_name))); | ||
152 | } | ||
153 | None | ||
154 | } | ||
155 | |||
156 | // FIXME: This should rely on semantic info. | ||
157 | fn ty_ctor(ty: &ast::Type, ctor: &str) -> Option<String> { | ||
158 | let res = ty.to_string().strip_prefix(ctor)?.strip_prefix('<')?.strip_suffix('>')?.to_string(); | ||
159 | Some(res) | ||
160 | } | ||
161 | |||
87 | #[cfg(test)] | 162 | #[cfg(test)] |
88 | mod tests { | 163 | mod tests { |
89 | use crate::tests::{check_assist, check_assist_not_applicable}; | 164 | use crate::tests::{check_assist, check_assist_not_applicable}; |
90 | 165 | ||
91 | use super::*; | 166 | use super::*; |
92 | 167 | ||
93 | fn check_not_applicable(ra_fixture: &str) { | ||
94 | check_assist_not_applicable(generate_getter, ra_fixture) | ||
95 | } | ||
96 | |||
97 | #[test] | 168 | #[test] |
98 | fn test_generate_getter_from_field() { | 169 | fn test_generate_getter_from_field() { |
99 | check_assist( | 170 | check_assist( |
100 | generate_getter, | 171 | generate_getter, |
101 | r#" | 172 | r#" |
102 | struct Context<T: Clone> { | 173 | struct Context { |
103 | dat$0a: T, | 174 | dat$0a: Data, |
104 | }"#, | 175 | } |
176 | "#, | ||
105 | r#" | 177 | r#" |
106 | struct Context<T: Clone> { | 178 | struct Context { |
107 | data: T, | 179 | data: Data, |
108 | } | 180 | } |
109 | 181 | ||
110 | impl<T: Clone> Context<T> { | 182 | impl Context { |
111 | /// Get a reference to the context's data. | 183 | /// Get a reference to the context's data. |
112 | fn data(&self) -> &T { | 184 | fn $0data(&self) -> &Data { |
113 | &self.data | 185 | &self.data |
114 | } | 186 | } |
115 | }"#, | 187 | } |
188 | "#, | ||
189 | ); | ||
190 | |||
191 | check_assist( | ||
192 | generate_getter_mut, | ||
193 | r#" | ||
194 | struct Context { | ||
195 | dat$0a: Data, | ||
196 | } | ||
197 | "#, | ||
198 | r#" | ||
199 | struct Context { | ||
200 | data: Data, | ||
201 | } | ||
202 | |||
203 | impl Context { | ||
204 | /// Get a mutable reference to the context's data. | ||
205 | fn $0data_mut(&mut self) -> &mut Data { | ||
206 | &mut self.data | ||
207 | } | ||
208 | } | ||
209 | "#, | ||
116 | ); | 210 | ); |
117 | } | 211 | } |
118 | 212 | ||
119 | #[test] | 213 | #[test] |
120 | fn test_generate_getter_already_implemented() { | 214 | fn test_generate_getter_already_implemented() { |
121 | check_not_applicable( | 215 | check_assist_not_applicable( |
216 | generate_getter, | ||
122 | r#" | 217 | r#" |
123 | struct Context<T: Clone> { | 218 | struct Context { |
124 | dat$0a: T, | 219 | dat$0a: Data, |
125 | } | 220 | } |
126 | 221 | ||
127 | impl<T: Clone> Context<T> { | 222 | impl Context { |
128 | fn data(&self) -> &T { | 223 | fn data(&self) -> &Data { |
129 | &self.data | 224 | &self.data |
130 | } | 225 | } |
131 | }"#, | 226 | } |
227 | "#, | ||
228 | ); | ||
229 | |||
230 | check_assist_not_applicable( | ||
231 | generate_getter_mut, | ||
232 | r#" | ||
233 | struct Context { | ||
234 | dat$0a: Data, | ||
235 | } | ||
236 | |||
237 | impl Context { | ||
238 | fn data_mut(&mut self) -> &mut Data { | ||
239 | &mut self.data | ||
240 | } | ||
241 | } | ||
242 | "#, | ||
132 | ); | 243 | ); |
133 | } | 244 | } |
134 | 245 | ||
@@ -137,20 +248,22 @@ impl<T: Clone> Context<T> { | |||
137 | check_assist( | 248 | check_assist( |
138 | generate_getter, | 249 | generate_getter, |
139 | r#" | 250 | r#" |
140 | pub(crate) struct Context<T: Clone> { | 251 | pub(crate) struct Context { |
141 | dat$0a: T, | 252 | dat$0a: Data, |
142 | }"#, | 253 | } |
254 | "#, | ||
143 | r#" | 255 | r#" |
144 | pub(crate) struct Context<T: Clone> { | 256 | pub(crate) struct Context { |
145 | data: T, | 257 | data: Data, |
146 | } | 258 | } |
147 | 259 | ||
148 | impl<T: Clone> Context<T> { | 260 | impl Context { |
149 | /// Get a reference to the context's data. | 261 | /// Get a reference to the context's data. |
150 | pub(crate) fn data(&self) -> &T { | 262 | pub(crate) fn $0data(&self) -> &Data { |
151 | &self.data | 263 | &self.data |
152 | } | 264 | } |
153 | }"#, | 265 | } |
266 | "#, | ||
154 | ); | 267 | ); |
155 | } | 268 | } |
156 | 269 | ||
@@ -159,34 +272,105 @@ impl<T: Clone> Context<T> { | |||
159 | check_assist( | 272 | check_assist( |
160 | generate_getter, | 273 | generate_getter, |
161 | r#" | 274 | r#" |
162 | struct Context<T: Clone> { | 275 | struct Context { |
163 | data: T, | 276 | data: Data, |
164 | cou$0nt: usize, | 277 | cou$0nt: usize, |
165 | } | 278 | } |
166 | 279 | ||
167 | impl<T: Clone> Context<T> { | 280 | impl Context { |
168 | /// Get a reference to the context's data. | 281 | /// Get a reference to the context's data. |
169 | fn data(&self) -> &T { | 282 | fn data(&self) -> &Data { |
170 | &self.data | 283 | &self.data |
171 | } | 284 | } |
172 | }"#, | 285 | } |
286 | "#, | ||
173 | r#" | 287 | r#" |
174 | struct Context<T: Clone> { | 288 | struct Context { |
175 | data: T, | 289 | data: Data, |
176 | count: usize, | 290 | count: usize, |
177 | } | 291 | } |
178 | 292 | ||
179 | impl<T: Clone> Context<T> { | 293 | impl Context { |
180 | /// Get a reference to the context's data. | 294 | /// Get a reference to the context's data. |
181 | fn data(&self) -> &T { | 295 | fn data(&self) -> &Data { |
182 | &self.data | 296 | &self.data |
183 | } | 297 | } |
184 | 298 | ||
185 | /// Get a reference to the context's count. | 299 | /// Get a reference to the context's count. |
186 | fn count(&self) -> &usize { | 300 | fn $0count(&self) -> &usize { |
187 | &self.count | 301 | &self.count |
188 | } | 302 | } |
189 | }"#, | 303 | } |
304 | "#, | ||
305 | ); | ||
306 | } | ||
307 | |||
308 | #[test] | ||
309 | fn test_special_cases() { | ||
310 | cov_mark::check!(useless_type_special_case); | ||
311 | check_assist( | ||
312 | generate_getter, | ||
313 | r#" | ||
314 | struct S { foo: $0String } | ||
315 | "#, | ||
316 | r#" | ||
317 | struct S { foo: String } | ||
318 | |||
319 | impl S { | ||
320 | /// Get a reference to the s's foo. | ||
321 | fn $0foo(&self) -> &str { | ||
322 | self.foo.as_str() | ||
323 | } | ||
324 | } | ||
325 | "#, | ||
326 | ); | ||
327 | check_assist( | ||
328 | generate_getter, | ||
329 | r#" | ||
330 | struct S { foo: $0Box<Sweets> } | ||
331 | "#, | ||
332 | r#" | ||
333 | struct S { foo: Box<Sweets> } | ||
334 | |||
335 | impl S { | ||
336 | /// Get a reference to the s's foo. | ||
337 | fn $0foo(&self) -> &Sweets { | ||
338 | self.foo.as_ref() | ||
339 | } | ||
340 | } | ||
341 | "#, | ||
342 | ); | ||
343 | check_assist( | ||
344 | generate_getter, | ||
345 | r#" | ||
346 | struct S { foo: $0Vec<()> } | ||
347 | "#, | ||
348 | r#" | ||
349 | struct S { foo: Vec<()> } | ||
350 | |||
351 | impl S { | ||
352 | /// Get a reference to the s's foo. | ||
353 | fn $0foo(&self) -> &[()] { | ||
354 | self.foo.as_slice() | ||
355 | } | ||
356 | } | ||
357 | "#, | ||
358 | ); | ||
359 | check_assist( | ||
360 | generate_getter, | ||
361 | r#" | ||
362 | struct S { foo: $0Option<Failure> } | ||
363 | "#, | ||
364 | r#" | ||
365 | struct S { foo: Option<Failure> } | ||
366 | |||
367 | impl S { | ||
368 | /// Get a reference to the s's foo. | ||
369 | fn $0foo(&self) -> Option<&Failure> { | ||
370 | self.foo.as_ref() | ||
371 | } | ||
372 | } | ||
373 | "#, | ||
190 | ); | 374 | ); |
191 | } | 375 | } |
192 | } | 376 | } |