1use ide_db::assists::GroupLabel;
2use itertools::Itertools;
3use stdx::to_lower_snake_case;
4use syntax::ast::HasVisibility;
5use syntax::ast::{self, AstNode, HasName};
6
7use crate::{
8 AssistContext, AssistId, Assists,
9 utils::{add_method_to_adt, find_struct_impl, is_selected},
10};
11
12pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
42 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
43 let parent_enum = ast::Adt::Enum(variant.parent_enum());
44 let variants = variant
45 .parent_enum()
46 .variant_list()?
47 .variants()
48 .filter(|it| is_selected(it, ctx.selection_trimmed(), true))
49 .collect::<Vec<_>>();
50 let methods = variants.iter().map(Method::new).collect::<Option<Vec<_>>>()?;
51 let enum_name = parent_enum.name()?;
52 let enum_lowercase_name = to_lower_snake_case(&enum_name.to_string()).replace('_', " ");
53 let fn_names = methods.iter().map(|it| it.fn_name.clone()).collect::<Vec<_>>();
54 stdx::never!(variants.is_empty());
55
56 let impl_def = find_struct_impl(ctx, &parent_enum, &fn_names)?;
58
59 let target = variant.syntax().text_range();
60 acc.add_group(
61 &GroupLabel("Generate an `is_`,`as_`, or `try_into_` for this enum variant".to_owned()),
62 AssistId::generate("generate_enum_is_method"),
63 "Generate an `is_` method for this enum variant",
64 target,
65 |builder| {
66 let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{v} "));
67 let method = methods
68 .iter()
69 .map(|Method { pattern_suffix, fn_name, variant_name }| {
70 format!(
71 " \
72 /// Returns `true` if the {enum_lowercase_name} is [`{variant_name}`].
73 ///
74 /// [`{variant_name}`]: {enum_name}::{variant_name}
75 #[must_use]
76 {vis}fn {fn_name}(&self) -> bool {{
77 matches!(self, Self::{variant_name}{pattern_suffix})
78 }}",
79 )
80 })
81 .join("\n\n");
82
83 add_method_to_adt(builder, &parent_enum, impl_def, &method);
84 },
85 )
86}
87
88struct Method {
89 pattern_suffix: &'static str,
90 fn_name: String,
91 variant_name: ast::Name,
92}
93
94impl Method {
95 fn new(variant: &ast::Variant) -> Option<Self> {
96 let pattern_suffix = match variant.kind() {
97 ast::StructKind::Record(_) => " { .. }",
98 ast::StructKind::Tuple(_) => "(..)",
99 ast::StructKind::Unit => "",
100 };
101
102 let variant_name = variant.name()?;
103 let fn_name = format!("is_{}", &to_lower_snake_case(&variant_name.text()));
104 Some(Method { pattern_suffix, fn_name, variant_name })
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use crate::tests::{check_assist, check_assist_not_applicable};
111
112 use super::*;
113
114 #[test]
115 fn test_generate_enum_is_from_variant() {
116 check_assist(
117 generate_enum_is_method,
118 r#"
119enum Variant {
120 Undefined,
121 Minor$0,
122 Major,
123}"#,
124 r#"enum Variant {
125 Undefined,
126 Minor,
127 Major,
128}
129
130impl Variant {
131 /// Returns `true` if the variant is [`Minor`].
132 ///
133 /// [`Minor`]: Variant::Minor
134 #[must_use]
135 fn is_minor(&self) -> bool {
136 matches!(self, Self::Minor)
137 }
138}"#,
139 );
140 }
141
142 #[test]
143 fn test_generate_enum_is_from_multiple_variant() {
144 check_assist(
145 generate_enum_is_method,
146 r#"
147enum Variant {
148 Undefined,
149 $0Minor,
150 M$0ajor,
151}"#,
152 r#"enum Variant {
153 Undefined,
154 Minor,
155 Major,
156}
157
158impl Variant {
159 /// Returns `true` if the variant is [`Minor`].
160 ///
161 /// [`Minor`]: Variant::Minor
162 #[must_use]
163 fn is_minor(&self) -> bool {
164 matches!(self, Self::Minor)
165 }
166
167 /// Returns `true` if the variant is [`Major`].
168 ///
169 /// [`Major`]: Variant::Major
170 #[must_use]
171 fn is_major(&self) -> bool {
172 matches!(self, Self::Major)
173 }
174}"#,
175 );
176 }
177
178 #[test]
179 fn test_generate_enum_is_already_implemented() {
180 check_assist_not_applicable(
181 generate_enum_is_method,
182 r#"
183enum Variant {
184 Undefined,
185 Minor$0,
186 Major,
187}
188
189impl Variant {
190 fn is_minor(&self) -> bool {
191 matches!(self, Self::Minor)
192 }
193}"#,
194 );
195 }
196
197 #[test]
198 fn test_generate_enum_is_from_tuple_variant() {
199 check_assist(
200 generate_enum_is_method,
201 r#"
202enum Variant {
203 Undefined,
204 Minor(u32)$0,
205 Major,
206}"#,
207 r#"enum Variant {
208 Undefined,
209 Minor(u32),
210 Major,
211}
212
213impl Variant {
214 /// Returns `true` if the variant is [`Minor`].
215 ///
216 /// [`Minor`]: Variant::Minor
217 #[must_use]
218 fn is_minor(&self) -> bool {
219 matches!(self, Self::Minor(..))
220 }
221}"#,
222 );
223 }
224
225 #[test]
226 fn test_generate_enum_is_from_record_variant() {
227 check_assist(
228 generate_enum_is_method,
229 r#"
230enum Variant {
231 Undefined,
232 Minor { foo: i32 }$0,
233 Major,
234}"#,
235 r#"enum Variant {
236 Undefined,
237 Minor { foo: i32 },
238 Major,
239}
240
241impl Variant {
242 /// Returns `true` if the variant is [`Minor`].
243 ///
244 /// [`Minor`]: Variant::Minor
245 #[must_use]
246 fn is_minor(&self) -> bool {
247 matches!(self, Self::Minor { .. })
248 }
249}"#,
250 );
251 }
252
253 #[test]
254 fn test_generate_enum_is_from_variant_with_one_variant() {
255 check_assist(
256 generate_enum_is_method,
257 r#"enum Variant { Undefi$0ned }"#,
258 r#"
259enum Variant { Undefined }
260
261impl Variant {
262 /// Returns `true` if the variant is [`Undefined`].
263 ///
264 /// [`Undefined`]: Variant::Undefined
265 #[must_use]
266 fn is_undefined(&self) -> bool {
267 matches!(self, Self::Undefined)
268 }
269}"#,
270 );
271 }
272
273 #[test]
274 fn test_generate_enum_is_from_variant_with_visibility_marker() {
275 check_assist(
276 generate_enum_is_method,
277 r#"
278pub(crate) enum Variant {
279 Undefined,
280 Minor$0,
281 Major,
282}"#,
283 r#"pub(crate) enum Variant {
284 Undefined,
285 Minor,
286 Major,
287}
288
289impl Variant {
290 /// Returns `true` if the variant is [`Minor`].
291 ///
292 /// [`Minor`]: Variant::Minor
293 #[must_use]
294 pub(crate) fn is_minor(&self) -> bool {
295 matches!(self, Self::Minor)
296 }
297}"#,
298 );
299 }
300
301 #[test]
302 fn test_multiple_generate_enum_is_from_variant() {
303 check_assist(
304 generate_enum_is_method,
305 r#"
306enum Variant {
307 Undefined,
308 Minor,
309 Major$0,
310}
311
312impl Variant {
313 /// Returns `true` if the variant is [`Minor`].
314 ///
315 /// [`Minor`]: Variant::Minor
316 #[must_use]
317 fn is_minor(&self) -> bool {
318 matches!(self, Self::Minor)
319 }
320}"#,
321 r#"enum Variant {
322 Undefined,
323 Minor,
324 Major,
325}
326
327impl Variant {
328 /// Returns `true` if the variant is [`Minor`].
329 ///
330 /// [`Minor`]: Variant::Minor
331 #[must_use]
332 fn is_minor(&self) -> bool {
333 matches!(self, Self::Minor)
334 }
335
336 /// Returns `true` if the variant is [`Major`].
337 ///
338 /// [`Major`]: Variant::Major
339 #[must_use]
340 fn is_major(&self) -> bool {
341 matches!(self, Self::Major)
342 }
343}"#,
344 );
345 }
346
347 #[test]
348 fn test_generate_enum_is_variant_names() {
349 check_assist(
350 generate_enum_is_method,
351 r#"
352enum CoroutineState {
353 Yielded,
354 Complete$0,
355 Major,
356}"#,
357 r#"enum CoroutineState {
358 Yielded,
359 Complete,
360 Major,
361}
362
363impl CoroutineState {
364 /// Returns `true` if the coroutine state is [`Complete`].
365 ///
366 /// [`Complete`]: CoroutineState::Complete
367 #[must_use]
368 fn is_complete(&self) -> bool {
369 matches!(self, Self::Complete)
370 }
371}"#,
372 );
373 }
374}