diff options
-rw-r--r-- | crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | 225 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 25 |
3 files changed, 252 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs new file mode 100644 index 000000000..bd29dddb3 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | |||
@@ -0,0 +1,225 @@ | |||
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 | } | ||
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index f1aab74d4..8c068a6c0 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -129,6 +129,7 @@ mod handlers { | |||
129 | mod flip_trait_bound; | 129 | mod flip_trait_bound; |
130 | mod generate_default_from_enum_variant; | 130 | mod generate_default_from_enum_variant; |
131 | mod generate_default_from_new; | 131 | mod generate_default_from_new; |
132 | mod generate_is_empty_from_len; | ||
132 | mod generate_derive; | 133 | mod generate_derive; |
133 | mod generate_enum_is_method; | 134 | mod generate_enum_is_method; |
134 | mod generate_enum_projection_method; | 135 | mod generate_enum_projection_method; |
@@ -193,6 +194,7 @@ mod handlers { | |||
193 | flip_trait_bound::flip_trait_bound, | 194 | flip_trait_bound::flip_trait_bound, |
194 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 195 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
195 | generate_default_from_new::generate_default_from_new, | 196 | generate_default_from_new::generate_default_from_new, |
197 | generate_is_empty_from_len::generate_is_empty_from_len, | ||
196 | generate_derive::generate_derive, | 198 | generate_derive::generate_derive, |
197 | generate_enum_is_method::generate_enum_is_method, | 199 | generate_enum_is_method::generate_enum_is_method, |
198 | generate_enum_projection_method::generate_enum_as_method, | 200 | generate_enum_projection_method::generate_enum_as_method, |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 3f77edd8d..66fbcc968 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -722,6 +722,31 @@ impl<T: Clone> Ctx<T> { | |||
722 | } | 722 | } |
723 | 723 | ||
724 | #[test] | 724 | #[test] |
725 | fn doctest_generate_is_empty_from_len() { | ||
726 | check_doc_test( | ||
727 | "generate_is_empty_from_len", | ||
728 | r#####" | ||
729 | impl MyStruct { | ||
730 | p$0ub fn len(&self) -> usize { | ||
731 | self.data.len() | ||
732 | } | ||
733 | } | ||
734 | "#####, | ||
735 | r#####" | ||
736 | impl MyStruct { | ||
737 | pub fn len(&self) -> usize { | ||
738 | self.data.len() | ||
739 | } | ||
740 | |||
741 | pub fn is_empty(&self) -> bool { | ||
742 | self.len() == 0 | ||
743 | } | ||
744 | } | ||
745 | "#####, | ||
746 | ) | ||
747 | } | ||
748 | |||
749 | #[test] | ||
725 | fn doctest_generate_new() { | 750 | fn doctest_generate_new() { |
726 | check_doc_test( | 751 | check_doc_test( |
727 | "generate_new", | 752 | "generate_new", |