]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/generate_is_empty_from_len.rs
7709: Added the assist to generate is_empty function
[rust.git] / crates / ide_assists / src / handlers / generate_is_empty_from_len.rs
1 use hir::{AssocItem, HasSource, Impl};
2 use syntax::{
3     ast::{self, NameOwner},
4     AstNode, TextRange,
5 };
6
7 use crate::{
8     assist_context::{AssistContext, Assists},
9     AssistId, AssistKind,
10 };
11
12 // Assist: generate_is_empty_from_len
13 //
14 // Generates is_empty implementation from the len method.
15 //
16 // ```
17 // impl MyStruct {
18 //     p$0ub fn len(&self) -> usize {
19 //         self.data.len()
20 //     }
21 // }
22 // ```
23 // ->
24 // ```
25 // impl MyStruct {
26 //     pub fn len(&self) -> usize {
27 //         self.data.len()
28 //     }
29 //
30 //     pub fn is_empty(&self) -> bool {
31 //         self.len() == 0
32 //     }
33 // }
34 // ```
35 pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36     let fn_node = ctx.find_node_at_offset::<ast::Fn>()?;
37     let fn_name = fn_node.name()?;
38
39     if fn_name.text() != "len" {
40         cov_mark::hit!(len_function_not_present);
41         return None;
42     }
43
44     if fn_node.param_list()?.params().next().is_some() {
45         cov_mark::hit!(len_function_with_parameters);
46         return None;
47     }
48
49     let impl_ = fn_node.syntax().ancestors().into_iter().find_map(ast::Impl::cast)?;
50     let impl_def = ctx.sema.to_def(&impl_)?;
51     if is_empty_implemented(ctx, &impl_def) {
52         cov_mark::hit!(is_empty_already_implemented);
53         return None;
54     }
55
56     let range = get_text_range_of_len_function(ctx, &impl_def)?;
57
58     acc.add(
59         AssistId("generate_is_empty_from_len", AssistKind::Generate),
60         "Generate a is_empty impl from a len function",
61         range,
62         |builder| {
63             let code = get_is_empty_code();
64             builder.insert(range.end(), code)
65         },
66     )
67 }
68
69 fn get_function_from_impl(ctx: &AssistContext, impl_def: &Impl, name: &str) -> Option<AssocItem> {
70     let db = ctx.sema.db;
71     impl_def.items(db).into_iter().filter(|item| matches!(item, AssocItem::Function(_value))).find(
72         |func| match func.name(db) {
73             Some(fn_name) => fn_name.to_string() == name,
74             None => false,
75         },
76     )
77 }
78
79 fn is_empty_implemented(ctx: &AssistContext, impl_def: &Impl) -> bool {
80     get_function_from_impl(ctx, impl_def, "is_empty").is_some()
81 }
82
83 fn get_text_range_of_len_function(ctx: &AssistContext, impl_def: &Impl) -> Option<TextRange> {
84     let db = ctx.sema.db;
85     let len_fn = get_function_from_impl(ctx, impl_def, "len")?;
86
87     let mut range = None;
88     if let AssocItem::Function(node) = len_fn {
89         let node = node.source(db)?;
90         range = Some(node.syntax().value.text_range());
91     }
92
93     range
94 }
95
96 fn get_is_empty_code() -> String {
97     r#"
98
99     pub fn is_empty(&self) -> bool {
100         self.len() == 0
101     }"#
102     .to_string()
103 }
104
105 #[cfg(test)]
106 mod tests {
107     use crate::tests::{check_assist, check_assist_not_applicable};
108
109     use super::*;
110
111     #[test]
112     fn len_function_not_present() {
113         cov_mark::check!(len_function_not_present);
114         check_assist_not_applicable(
115             generate_is_empty_from_len,
116             r#"
117 impl MyStruct {
118     p$0ub fn test(&self) -> usize {
119             self.data.len()
120         }
121     }
122 "#,
123         );
124     }
125
126     #[test]
127     fn len_function_with_parameters() {
128         cov_mark::check!(len_function_with_parameters);
129         check_assist_not_applicable(
130             generate_is_empty_from_len,
131             r#"
132 impl MyStruct {
133     p$0ub fn len(&self, _i: bool) -> usize {
134         self.data.len()
135     }
136 }
137 "#,
138         );
139     }
140
141     #[test]
142     fn is_empty_already_implemented() {
143         cov_mark::check!(is_empty_already_implemented);
144         check_assist_not_applicable(
145             generate_is_empty_from_len,
146             r#"
147 impl MyStruct {
148     p$0ub fn len(&self) -> usize {
149         self.data.len()
150     }
151
152     pub fn is_empty(&self) -> bool {
153         self.len() == 0
154     }
155 }
156 "#,
157         );
158     }
159
160     #[test]
161     fn generate_is_empty() {
162         check_assist(
163             generate_is_empty_from_len,
164             r#"
165 impl MyStruct {
166     p$0ub fn len(&self) -> usize {
167         self.data.len()
168     }
169 }
170 "#,
171             r#"
172 impl MyStruct {
173     pub fn len(&self) -> usize {
174         self.data.len()
175     }
176
177     pub fn is_empty(&self) -> bool {
178         self.len() == 0
179     }
180 }
181 "#,
182         );
183     }
184
185     #[test]
186     fn multiple_functions_in_impl() {
187         check_assist(
188             generate_is_empty_from_len,
189             r#"
190 impl MyStruct {
191     pub fn new() -> Self {
192         Self { data: 0 }
193     }
194
195     p$0ub fn len(&self) -> usize {
196         self.data.len()
197     }
198
199     pub fn work(&self) -> Option<usize> {
200         // do some work
201     }
202 }
203 "#,
204             r#"
205 impl MyStruct {
206     pub fn new() -> Self {
207         Self { data: 0 }
208     }
209
210     pub fn len(&self) -> usize {
211         self.data.len()
212     }
213
214     pub fn is_empty(&self) -> bool {
215         self.len() == 0
216     }
217
218     pub fn work(&self) -> Option<usize> {
219         // do some work
220     }
221 }
222 "#,
223         );
224     }
225 }