aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/generate_enum_projection_method.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/generate_enum_projection_method.rs')
-rw-r--r--crates/ide_assists/src/handlers/generate_enum_projection_method.rs307
1 files changed, 307 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/generate_enum_projection_method.rs b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs
new file mode 100644
index 000000000..71447f310
--- /dev/null
+++ b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs
@@ -0,0 +1,307 @@
1use itertools::Itertools;
2use stdx::to_lower_snake_case;
3use syntax::ast::VisibilityOwner;
4use syntax::ast::{self, AstNode, NameOwner};
5
6use crate::{
7 utils::{add_method_to_adt, find_struct_impl},
8 AssistContext, AssistId, AssistKind, Assists,
9};
10
11// Assist: generate_enum_into_method
12//
13// Generate an `into_` method for an enum variant.
14//
15// ```
16// enum Value {
17// Number(i32),
18// Text(String)$0,
19// }
20// ```
21// ->
22// ```
23// enum Value {
24// Number(i32),
25// Text(String),
26// }
27//
28// impl Value {
29// fn into_text(self) -> Option<String> {
30// if let Self::Text(v) = self {
31// Some(v)
32// } else {
33// None
34// }
35// }
36// }
37// ```
38pub(crate) fn generate_enum_into_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 generate_enum_projection_method(
40 acc,
41 ctx,
42 "generate_enum_into_method",
43 "Generate an `into_` method for an enum variant",
44 "into",
45 "",
46 )
47}
48
49// Assist: generate_enum_as_method
50//
51// Generate an `as_` method for an enum variant.
52//
53// ```
54// enum Value {
55// Number(i32),
56// Text(String)$0,
57// }
58// ```
59// ->
60// ```
61// enum Value {
62// Number(i32),
63// Text(String),
64// }
65//
66// impl Value {
67// fn as_text(&self) -> Option<&String> {
68// if let Self::Text(v) = self {
69// Some(v)
70// } else {
71// None
72// }
73// }
74// }
75// ```
76pub(crate) fn generate_enum_as_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
77 generate_enum_projection_method(
78 acc,
79 ctx,
80 "generate_enum_as_method",
81 "Generate an `as_` method for an enum variant",
82 "as",
83 "&",
84 )
85}
86
87pub(crate) fn generate_enum_projection_method(
88 acc: &mut Assists,
89 ctx: &AssistContext,
90 assist_id: &'static str,
91 assist_description: &str,
92 fn_name_prefix: &str,
93 ref_prefix: &str,
94) -> Option<()> {
95 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
96 let variant_name = variant.name()?;
97 let parent_enum = ast::Adt::Enum(variant.parent_enum());
98
99 let (pattern_suffix, field_type, bound_name) = match variant.kind() {
100 ast::StructKind::Record(record) => {
101 let (field,) = record.fields().collect_tuple()?;
102 let name = field.name()?.to_string();
103 let ty = field.ty()?;
104 let pattern_suffix = format!(" {{ {} }}", name);
105 (pattern_suffix, ty, name)
106 }
107 ast::StructKind::Tuple(tuple) => {
108 let (field,) = tuple.fields().collect_tuple()?;
109 let ty = field.ty()?;
110 ("(v)".to_owned(), ty, "v".to_owned())
111 }
112 ast::StructKind::Unit => return None,
113 };
114
115 let fn_name = format!("{}_{}", fn_name_prefix, &to_lower_snake_case(variant_name.text()));
116
117 // Return early if we've found an existing new fn
118 let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?;
119
120 let target = variant.syntax().text_range();
121 acc.add(AssistId(assist_id, AssistKind::Generate), assist_description, target, |builder| {
122 let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v));
123 let method = format!(
124 " {0}fn {1}({2}self) -> Option<{2}{3}> {{
125 if let Self::{4}{5} = self {{
126 Some({6})
127 }} else {{
128 None
129 }}
130 }}",
131 vis,
132 fn_name,
133 ref_prefix,
134 field_type.syntax(),
135 variant_name,
136 pattern_suffix,
137 bound_name,
138 );
139
140 add_method_to_adt(builder, &parent_enum, impl_def, &method);
141 })
142}
143
144#[cfg(test)]
145mod tests {
146 use crate::tests::{check_assist, check_assist_not_applicable};
147
148 use super::*;
149
150 #[test]
151 fn test_generate_enum_into_tuple_variant() {
152 check_assist(
153 generate_enum_into_method,
154 r#"
155enum Value {
156 Number(i32),
157 Text(String)$0,
158}"#,
159 r#"enum Value {
160 Number(i32),
161 Text(String),
162}
163
164impl Value {
165 fn into_text(self) -> Option<String> {
166 if let Self::Text(v) = self {
167 Some(v)
168 } else {
169 None
170 }
171 }
172}"#,
173 );
174 }
175
176 #[test]
177 fn test_generate_enum_into_already_implemented() {
178 check_assist_not_applicable(
179 generate_enum_into_method,
180 r#"enum Value {
181 Number(i32),
182 Text(String)$0,
183}
184
185impl Value {
186 fn into_text(self) -> Option<String> {
187 if let Self::Text(v) = self {
188 Some(v)
189 } else {
190 None
191 }
192 }
193}"#,
194 );
195 }
196
197 #[test]
198 fn test_generate_enum_into_unit_variant() {
199 check_assist_not_applicable(
200 generate_enum_into_method,
201 r#"enum Value {
202 Number(i32),
203 Text(String),
204 Unit$0,
205}"#,
206 );
207 }
208
209 #[test]
210 fn test_generate_enum_into_record_with_multiple_fields() {
211 check_assist_not_applicable(
212 generate_enum_into_method,
213 r#"enum Value {
214 Number(i32),
215 Text(String),
216 Both { first: i32, second: String }$0,
217}"#,
218 );
219 }
220
221 #[test]
222 fn test_generate_enum_into_tuple_with_multiple_fields() {
223 check_assist_not_applicable(
224 generate_enum_into_method,
225 r#"enum Value {
226 Number(i32),
227 Text(String, String)$0,
228}"#,
229 );
230 }
231
232 #[test]
233 fn test_generate_enum_into_record_variant() {
234 check_assist(
235 generate_enum_into_method,
236 r#"enum Value {
237 Number(i32),
238 Text { text: String }$0,
239}"#,
240 r#"enum Value {
241 Number(i32),
242 Text { text: String },
243}
244
245impl Value {
246 fn into_text(self) -> Option<String> {
247 if let Self::Text { text } = self {
248 Some(text)
249 } else {
250 None
251 }
252 }
253}"#,
254 );
255 }
256
257 #[test]
258 fn test_generate_enum_as_tuple_variant() {
259 check_assist(
260 generate_enum_as_method,
261 r#"
262enum Value {
263 Number(i32),
264 Text(String)$0,
265}"#,
266 r#"enum Value {
267 Number(i32),
268 Text(String),
269}
270
271impl Value {
272 fn as_text(&self) -> Option<&String> {
273 if let Self::Text(v) = self {
274 Some(v)
275 } else {
276 None
277 }
278 }
279}"#,
280 );
281 }
282
283 #[test]
284 fn test_generate_enum_as_record_variant() {
285 check_assist(
286 generate_enum_as_method,
287 r#"enum Value {
288 Number(i32),
289 Text { text: String }$0,
290}"#,
291 r#"enum Value {
292 Number(i32),
293 Text { text: String },
294}
295
296impl Value {
297 fn as_text(&self) -> Option<&String> {
298 if let Self::Text { text } = self {
299 Some(text)
300 } else {
301 None
302 }
303 }
304}"#,
305 );
306 }
307}