aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/generate_is_empty_from_len.rs225
-rw-r--r--crates/ide_assists/src/lib.rs2
-rw-r--r--crates/ide_assists/src/tests/generated.rs25
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 @@
1use hir::{AssocItem, HasSource, Impl};
2use syntax::{
3 ast::{self, NameOwner},
4 AstNode, TextRange,
5};
6
7use 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// ```
35pub(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
69fn 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
79fn is_empty_implemented(ctx: &AssistContext, impl_def: &Impl) -> bool {
80 get_function_from_impl(ctx, impl_def, "is_empty").is_some()
81}
82
83fn 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
96fn 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)]
106mod 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#"
117impl 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#"
132impl 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#"
147impl 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#"
165impl MyStruct {
166 p$0ub fn len(&self) -> usize {
167 self.data.len()
168 }
169}
170"#,
171 r#"
172impl 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#"
190impl 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#"
205impl 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]
725fn doctest_generate_is_empty_from_len() {
726 check_doc_test(
727 "generate_is_empty_from_len",
728 r#####"
729impl MyStruct {
730 p$0ub fn len(&self) -> usize {
731 self.data.len()
732 }
733}
734"#####,
735 r#####"
736impl 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]
725fn doctest_generate_new() { 750fn doctest_generate_new() {
726 check_doc_test( 751 check_doc_test(
727 "generate_new", 752 "generate_new",