diff options
Diffstat (limited to 'crates/ide_assists/src/handlers/generate_getter.rs')
-rw-r--r-- | crates/ide_assists/src/handlers/generate_getter.rs | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/generate_getter.rs b/crates/ide_assists/src/handlers/generate_getter.rs new file mode 100644 index 000000000..df7d1bb95 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_getter.rs | |||
@@ -0,0 +1,192 @@ | |||
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 name(&self) -> &String { | ||
27 | // &self.name | ||
28 | // } | ||
29 | // } | ||
30 | // ``` | ||
31 | pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
32 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
33 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; | ||
34 | |||
35 | let strukt_name = strukt.name()?; | ||
36 | let field_name = field.name()?; | ||
37 | let field_ty = field.ty()?; | ||
38 | |||
39 | // Return early if we've found an existing fn | ||
40 | let fn_name = to_lower_snake_case(&field_name.to_string()); | ||
41 | let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?; | ||
42 | |||
43 | let target = field.syntax().text_range(); | ||
44 | acc.add_group( | ||
45 | &GroupLabel("Generate getter/setter".to_owned()), | ||
46 | AssistId("generate_getter", AssistKind::Generate), | ||
47 | "Generate a getter method", | ||
48 | target, | ||
49 | |builder| { | ||
50 | let mut buf = String::with_capacity(512); | ||
51 | |||
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() { | ||
57 | buf.push('\n'); | ||
58 | } | ||
59 | |||
60 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
61 | format_to!( | ||
62 | buf, | ||
63 | " /// Get a reference to the {}'s {}. | ||
64 | {}fn {}(&self) -> &{} {{ | ||
65 | &self.{} | ||
66 | }}", | ||
67 | strukt_name_spaced, | ||
68 | fn_name_spaced, | ||
69 | vis, | ||
70 | fn_name, | ||
71 | field_ty, | ||
72 | fn_name, | ||
73 | ); | ||
74 | |||
75 | let start_offset = impl_def | ||
76 | .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) | ||
77 | .unwrap_or_else(|| { | ||
78 | buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf); | ||
79 | strukt.syntax().text_range().end() | ||
80 | }); | ||
81 | |||
82 | builder.insert(start_offset, buf); | ||
83 | }, | ||
84 | ) | ||
85 | } | ||
86 | |||
87 | #[cfg(test)] | ||
88 | mod tests { | ||
89 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
90 | |||
91 | use super::*; | ||
92 | |||
93 | fn check_not_applicable(ra_fixture: &str) { | ||
94 | check_assist_not_applicable(generate_getter, ra_fixture) | ||
95 | } | ||
96 | |||
97 | #[test] | ||
98 | fn test_generate_getter_from_field() { | ||
99 | check_assist( | ||
100 | generate_getter, | ||
101 | r#" | ||
102 | struct Context<T: Clone> { | ||
103 | dat$0a: T, | ||
104 | }"#, | ||
105 | r#" | ||
106 | struct Context<T: Clone> { | ||
107 | data: T, | ||
108 | } | ||
109 | |||
110 | impl<T: Clone> Context<T> { | ||
111 | /// Get a reference to the context's data. | ||
112 | fn data(&self) -> &T { | ||
113 | &self.data | ||
114 | } | ||
115 | }"#, | ||
116 | ); | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn test_generate_getter_already_implemented() { | ||
121 | check_not_applicable( | ||
122 | r#" | ||
123 | struct Context<T: Clone> { | ||
124 | dat$0a: T, | ||
125 | } | ||
126 | |||
127 | impl<T: Clone> Context<T> { | ||
128 | fn data(&self) -> &T { | ||
129 | &self.data | ||
130 | } | ||
131 | }"#, | ||
132 | ); | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn test_generate_getter_from_field_with_visibility_marker() { | ||
137 | check_assist( | ||
138 | generate_getter, | ||
139 | r#" | ||
140 | pub(crate) struct Context<T: Clone> { | ||
141 | dat$0a: T, | ||
142 | }"#, | ||
143 | r#" | ||
144 | pub(crate) struct Context<T: Clone> { | ||
145 | data: T, | ||
146 | } | ||
147 | |||
148 | impl<T: Clone> Context<T> { | ||
149 | /// Get a reference to the context's data. | ||
150 | pub(crate) fn data(&self) -> &T { | ||
151 | &self.data | ||
152 | } | ||
153 | }"#, | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn test_multiple_generate_getter() { | ||
159 | check_assist( | ||
160 | generate_getter, | ||
161 | r#" | ||
162 | struct Context<T: Clone> { | ||
163 | data: T, | ||
164 | cou$0nt: usize, | ||
165 | } | ||
166 | |||
167 | impl<T: Clone> Context<T> { | ||
168 | /// Get a reference to the context's data. | ||
169 | fn data(&self) -> &T { | ||
170 | &self.data | ||
171 | } | ||
172 | }"#, | ||
173 | r#" | ||
174 | struct Context<T: Clone> { | ||
175 | data: T, | ||
176 | count: usize, | ||
177 | } | ||
178 | |||
179 | impl<T: Clone> Context<T> { | ||
180 | /// Get a reference to the context's data. | ||
181 | fn data(&self) -> &T { | ||
182 | &self.data | ||
183 | } | ||
184 | |||
185 | /// Get a reference to the context's count. | ||
186 | fn count(&self) -> &usize { | ||
187 | &self.count | ||
188 | } | ||
189 | }"#, | ||
190 | ); | ||
191 | } | ||
192 | } | ||