]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/generate_getter.rs
feat: generate getter assist places the cursor at the generated function
[rust.git] / crates / ide_assists / src / handlers / generate_getter.rs
1 use stdx::{format_to, to_lower_snake_case};
2 use syntax::ast::{self, AstNode, NameOwner, VisibilityOwner};
3
4 use crate::{
5     utils::{find_impl_block_end, find_struct_impl, generate_impl_text},
6     AssistContext, AssistId, AssistKind, Assists, GroupLabel,
7 };
8
9 // Assist: generate_getter
10 //
11 // Generate a getter method.
12 //
13 // ```
14 // struct Person {
15 //     nam$0e: String,
16 // }
17 // ```
18 // ->
19 // ```
20 // struct Person {
21 //     name: String,
22 // }
23 //
24 // impl Person {
25 //     /// Get a reference to the person's name.
26 //     fn $0name(&self) -> &String {
27 //         &self.name
28 //     }
29 // }
30 // ```
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<()> {
66     let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
67     let field = ctx.find_node_at_offset::<ast::RecordField>()?;
68
69     let strukt_name = strukt.name()?;
70     let field_name = field.name()?;
71     let field_ty = field.ty()?;
72
73     // Return early if we've found an existing fn
74     let mut fn_name = to_lower_snake_case(&field_name.to_string());
75     if mutable {
76         format_to!(fn_name, "_mut");
77     }
78     let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?;
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     };
85     let target = field.syntax().text_range();
86     acc.add_group(
87         &GroupLabel("Generate getter/setter".to_owned()),
88         AssistId(id, AssistKind::Generate),
89         label,
90         target,
91         |builder| {
92             let mut buf = String::with_capacity(512);
93
94             if impl_def.is_some() {
95                 buf.push('\n');
96             }
97
98             let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
99             format_to!(
100                 buf,
101                 "    /// Get a {}reference to the {}'s {}.
102     {}fn {}(&{mut_}self) -> &{mut_}{} {{
103         &{mut_}self.{}
104     }}",
105                 mutable.then(|| "mutable ").unwrap_or_default(),
106                 to_lower_snake_case(&strukt_name.to_string()).replace('_', " "),
107                 fn_name.trim_end_matches("_mut").replace('_', " "),
108                 vis,
109                 fn_name,
110                 field_ty,
111                 field_name,
112                 mut_ = mutable.then(|| "mut ").unwrap_or_default(),
113             );
114
115             let start_offset = impl_def
116                 .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf))
117                 .unwrap_or_else(|| {
118                     buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf);
119                     strukt.syntax().text_range().end()
120                 });
121
122             match ctx.config.snippet_cap {
123                 Some(cap) => {
124                     builder.insert_snippet(cap, start_offset, buf.replacen("fn ", "fn $0", 1))
125                 }
126                 None => builder.insert(start_offset, buf),
127             }
128         },
129     )
130 }
131
132 #[cfg(test)]
133 mod tests {
134     use crate::tests::{check_assist, check_assist_not_applicable};
135
136     use super::*;
137
138     #[test]
139     fn test_generate_getter_from_field() {
140         check_assist(
141             generate_getter,
142             r#"
143 struct Context {
144     dat$0a: Data,
145 }
146 "#,
147             r#"
148 struct Context {
149     data: Data,
150 }
151
152 impl Context {
153     /// Get a reference to the context's data.
154     fn $0data(&self) -> &Data {
155         &self.data
156     }
157 }
158 "#,
159         );
160
161         check_assist(
162             generate_getter_mut,
163             r#"
164 struct Context {
165     dat$0a: Data,
166 }
167 "#,
168             r#"
169 struct Context {
170     data: Data,
171 }
172
173 impl Context {
174     /// Get a mutable reference to the context's data.
175     fn $0data_mut(&mut self) -> &mut Data {
176         &mut self.data
177     }
178 }
179 "#,
180         );
181     }
182
183     #[test]
184     fn test_generate_getter_already_implemented() {
185         check_assist_not_applicable(
186             generate_getter,
187             r#"
188 struct Context {
189     dat$0a: Data,
190 }
191
192 impl Context {
193     fn data(&self) -> &Data {
194         &self.data
195     }
196 }
197 "#,
198         );
199
200         check_assist_not_applicable(
201             generate_getter_mut,
202             r#"
203 struct Context {
204     dat$0a: Data,
205 }
206
207 impl Context {
208     fn data_mut(&mut self) -> &mut Data {
209         &mut self.data
210     }
211 }
212 "#,
213         );
214     }
215
216     #[test]
217     fn test_generate_getter_from_field_with_visibility_marker() {
218         check_assist(
219             generate_getter,
220             r#"
221 pub(crate) struct Context {
222     dat$0a: Data,
223 }
224 "#,
225             r#"
226 pub(crate) struct Context {
227     data: Data,
228 }
229
230 impl Context {
231     /// Get a reference to the context's data.
232     pub(crate) fn $0data(&self) -> &Data {
233         &self.data
234     }
235 }
236 "#,
237         );
238     }
239
240     #[test]
241     fn test_multiple_generate_getter() {
242         check_assist(
243             generate_getter,
244             r#"
245 struct Context {
246     data: Data,
247     cou$0nt: usize,
248 }
249
250 impl Context {
251     /// Get a reference to the context's data.
252     fn data(&self) -> &Data {
253         &self.data
254     }
255 }
256 "#,
257             r#"
258 struct Context {
259     data: Data,
260     count: usize,
261 }
262
263 impl Context {
264     /// Get a reference to the context's data.
265     fn data(&self) -> &Data {
266         &self.data
267     }
268
269     /// Get a reference to the context's count.
270     fn $0count(&self) -> &usize {
271         &self.count
272     }
273 }
274 "#,
275         );
276     }
277 }