diff options
Diffstat (limited to 'crates/ide_assists')
-rw-r--r-- | crates/ide_assists/Cargo.toml | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | 255 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 29 |
4 files changed, 287 insertions, 1 deletions
diff --git a/crates/ide_assists/Cargo.toml b/crates/ide_assists/Cargo.toml index dd9aa27c6..a83acb191 100644 --- a/crates/ide_assists/Cargo.toml +++ b/crates/ide_assists/Cargo.toml | |||
@@ -10,7 +10,7 @@ edition = "2018" | |||
10 | doctest = false | 10 | doctest = false |
11 | 11 | ||
12 | [dependencies] | 12 | [dependencies] |
13 | cov-mark = "1.1" | 13 | cov-mark = { version = "1.1", features = ["thread-local"] } |
14 | rustc-hash = "1.1.0" | 14 | rustc-hash = "1.1.0" |
15 | itertools = "0.10.0" | 15 | itertools = "0.10.0" |
16 | either = "1.6.1" | 16 | either = "1.6.1" |
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..aa7072f25 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_is_empty_from_len.rs | |||
@@ -0,0 +1,255 @@ | |||
1 | use hir::{known, HasSource, Name}; | ||
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 | // struct MyStruct { data: Vec<String> } | ||
18 | // | ||
19 | // impl MyStruct { | ||
20 | // p$0ub fn len(&self) -> usize { | ||
21 | // self.data.len() | ||
22 | // } | ||
23 | // } | ||
24 | // ``` | ||
25 | // -> | ||
26 | // ``` | ||
27 | // struct MyStruct { data: Vec<String> } | ||
28 | // | ||
29 | // impl MyStruct { | ||
30 | // pub fn len(&self) -> usize { | ||
31 | // self.data.len() | ||
32 | // } | ||
33 | // | ||
34 | // pub fn is_empty(&self) -> bool { | ||
35 | // self.len() == 0 | ||
36 | // } | ||
37 | // } | ||
38 | // ``` | ||
39 | pub(crate) fn generate_is_empty_from_len(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
40 | let fn_node = ctx.find_node_at_offset::<ast::Fn>()?; | ||
41 | let fn_name = fn_node.name()?; | ||
42 | |||
43 | if fn_name.text() != "len" { | ||
44 | cov_mark::hit!(len_function_not_present); | ||
45 | return None; | ||
46 | } | ||
47 | |||
48 | if fn_node.param_list()?.params().next().is_some() { | ||
49 | cov_mark::hit!(len_function_with_parameters); | ||
50 | return None; | ||
51 | } | ||
52 | |||
53 | let impl_ = fn_node.syntax().ancestors().find_map(ast::Impl::cast)?; | ||
54 | if get_impl_method(ctx, &impl_, &known::is_empty).is_some() { | ||
55 | cov_mark::hit!(is_empty_already_implemented); | ||
56 | return None; | ||
57 | } | ||
58 | |||
59 | let range = get_text_range_of_len_function(ctx, &impl_)?; | ||
60 | |||
61 | acc.add( | ||
62 | AssistId("generate_is_empty_from_len", AssistKind::Generate), | ||
63 | "Generate a is_empty impl from a len function", | ||
64 | range, | ||
65 | |builder| { | ||
66 | let code = r#" | ||
67 | |||
68 | pub fn is_empty(&self) -> bool { | ||
69 | self.len() == 0 | ||
70 | }"# | ||
71 | .to_string(); | ||
72 | builder.insert(range.end(), code) | ||
73 | }, | ||
74 | ) | ||
75 | } | ||
76 | |||
77 | fn get_impl_method( | ||
78 | ctx: &AssistContext, | ||
79 | impl_: &ast::Impl, | ||
80 | fn_name: &Name, | ||
81 | ) -> Option<hir::Function> { | ||
82 | let db = ctx.sema.db; | ||
83 | let impl_def: hir::Impl = ctx.sema.to_def(impl_)?; | ||
84 | |||
85 | let scope = ctx.sema.scope(impl_.syntax()); | ||
86 | let krate = impl_def.module(db).krate(); | ||
87 | let ty = impl_def.target_ty(db); | ||
88 | let traits_in_scope = scope.traits_in_scope(); | ||
89 | ty.iterate_method_candidates(db, krate, &traits_in_scope, Some(fn_name), |_, func| Some(func)) | ||
90 | } | ||
91 | |||
92 | fn get_text_range_of_len_function(ctx: &AssistContext, impl_: &ast::Impl) -> Option<TextRange> { | ||
93 | let db = ctx.sema.db; | ||
94 | let func = get_impl_method(ctx, impl_, &known::len)?; | ||
95 | let node = func.source(db)?; | ||
96 | Some(node.syntax().value.text_range()) | ||
97 | } | ||
98 | |||
99 | #[cfg(test)] | ||
100 | mod tests { | ||
101 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
102 | |||
103 | use super::*; | ||
104 | |||
105 | #[test] | ||
106 | fn len_function_not_present() { | ||
107 | cov_mark::check!(len_function_not_present); | ||
108 | check_assist_not_applicable( | ||
109 | generate_is_empty_from_len, | ||
110 | r#" | ||
111 | struct MyStruct { data: Vec<String> } | ||
112 | |||
113 | impl MyStruct { | ||
114 | p$0ub fn test(&self) -> usize { | ||
115 | self.data.len() | ||
116 | } | ||
117 | } | ||
118 | "#, | ||
119 | ); | ||
120 | } | ||
121 | |||
122 | #[test] | ||
123 | fn len_function_with_parameters() { | ||
124 | cov_mark::check!(len_function_with_parameters); | ||
125 | check_assist_not_applicable( | ||
126 | generate_is_empty_from_len, | ||
127 | r#" | ||
128 | struct MyStruct { data: Vec<String> } | ||
129 | |||
130 | impl MyStruct { | ||
131 | p$0ub fn len(&self, _i: bool) -> usize { | ||
132 | self.data.len() | ||
133 | } | ||
134 | } | ||
135 | "#, | ||
136 | ); | ||
137 | } | ||
138 | |||
139 | #[test] | ||
140 | fn is_empty_already_implemented() { | ||
141 | cov_mark::check!(is_empty_already_implemented); | ||
142 | check_assist_not_applicable( | ||
143 | generate_is_empty_from_len, | ||
144 | r#" | ||
145 | struct MyStruct { data: Vec<String> } | ||
146 | |||
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 | struct MyStruct { data: Vec<String> } | ||
166 | |||
167 | impl MyStruct { | ||
168 | p$0ub fn len(&self) -> usize { | ||
169 | self.data.len() | ||
170 | } | ||
171 | } | ||
172 | "#, | ||
173 | r#" | ||
174 | struct MyStruct { data: Vec<String> } | ||
175 | |||
176 | impl MyStruct { | ||
177 | pub fn len(&self) -> usize { | ||
178 | self.data.len() | ||
179 | } | ||
180 | |||
181 | pub fn is_empty(&self) -> bool { | ||
182 | self.len() == 0 | ||
183 | } | ||
184 | } | ||
185 | "#, | ||
186 | ); | ||
187 | } | ||
188 | |||
189 | #[test] | ||
190 | fn multiple_functions_in_impl() { | ||
191 | check_assist( | ||
192 | generate_is_empty_from_len, | ||
193 | r#" | ||
194 | struct MyStruct { data: Vec<String> } | ||
195 | |||
196 | impl MyStruct { | ||
197 | pub fn new() -> Self { | ||
198 | Self { data: 0 } | ||
199 | } | ||
200 | |||
201 | p$0ub fn len(&self) -> usize { | ||
202 | self.data.len() | ||
203 | } | ||
204 | |||
205 | pub fn work(&self) -> Option<usize> { | ||
206 | |||
207 | } | ||
208 | } | ||
209 | "#, | ||
210 | r#" | ||
211 | struct MyStruct { data: Vec<String> } | ||
212 | |||
213 | impl MyStruct { | ||
214 | pub fn new() -> Self { | ||
215 | Self { data: 0 } | ||
216 | } | ||
217 | |||
218 | pub fn len(&self) -> usize { | ||
219 | self.data.len() | ||
220 | } | ||
221 | |||
222 | pub fn is_empty(&self) -> bool { | ||
223 | self.len() == 0 | ||
224 | } | ||
225 | |||
226 | pub fn work(&self) -> Option<usize> { | ||
227 | |||
228 | } | ||
229 | } | ||
230 | "#, | ||
231 | ); | ||
232 | } | ||
233 | |||
234 | #[test] | ||
235 | fn multiple_impls() { | ||
236 | check_assist_not_applicable( | ||
237 | generate_is_empty_from_len, | ||
238 | r#" | ||
239 | struct MyStruct { data: Vec<String> } | ||
240 | |||
241 | impl MyStruct { | ||
242 | p$0ub fn len(&self) -> usize { | ||
243 | self.data.len() | ||
244 | } | ||
245 | } | ||
246 | |||
247 | impl MyStruct { | ||
248 | pub fn is_empty(&self) -> bool { | ||
249 | self.len() == 0 | ||
250 | } | ||
251 | } | ||
252 | "#, | ||
253 | ); | ||
254 | } | ||
255 | } | ||
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..736027ff0 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -722,6 +722,35 @@ 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 | struct MyStruct { data: Vec<String> } | ||
730 | |||
731 | impl MyStruct { | ||
732 | p$0ub fn len(&self) -> usize { | ||
733 | self.data.len() | ||
734 | } | ||
735 | } | ||
736 | "#####, | ||
737 | r#####" | ||
738 | struct MyStruct { data: Vec<String> } | ||
739 | |||
740 | impl MyStruct { | ||
741 | pub fn len(&self) -> usize { | ||
742 | self.data.len() | ||
743 | } | ||
744 | |||
745 | pub fn is_empty(&self) -> bool { | ||
746 | self.len() == 0 | ||
747 | } | ||
748 | } | ||
749 | "#####, | ||
750 | ) | ||
751 | } | ||
752 | |||
753 | #[test] | ||
725 | fn doctest_generate_new() { | 754 | fn doctest_generate_new() { |
726 | check_doc_test( | 755 | check_doc_test( |
727 | "generate_new", | 756 | "generate_new", |