diff options
Diffstat (limited to 'crates/ide_assists/src')
-rw-r--r-- | crates/ide_assists/src/handlers/generate_enum_is_method.rs (renamed from crates/ide_assists/src/handlers/generate_enum_match_method.rs) | 128 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/generate_enum_projection_method.rs | 331 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_for_loop_with_for_each.rs (renamed from crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs) | 38 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_let_with_if_let.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/lib.rs | 17 | ||||
-rw-r--r-- | crates/ide_assists/src/tests.rs | 2 | ||||
-rw-r--r-- | crates/ide_assists/src/tests/generated.rs | 108 | ||||
-rw-r--r-- | crates/ide_assists/src/utils.rs | 24 |
8 files changed, 547 insertions, 103 deletions
diff --git a/crates/ide_assists/src/handlers/generate_enum_match_method.rs b/crates/ide_assists/src/handlers/generate_enum_is_method.rs index aeb887e71..7e181a480 100644 --- a/crates/ide_assists/src/handlers/generate_enum_match_method.rs +++ b/crates/ide_assists/src/handlers/generate_enum_is_method.rs | |||
@@ -1,14 +1,13 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | 1 | use stdx::to_lower_snake_case; |
2 | use syntax::ast::VisibilityOwner; | 2 | use syntax::ast::VisibilityOwner; |
3 | use syntax::ast::{self, AstNode, NameOwner}; | 3 | use syntax::ast::{self, AstNode, NameOwner}; |
4 | use test_utils::mark; | ||
5 | 4 | ||
6 | use crate::{ | 5 | use crate::{ |
7 | utils::{find_impl_block_end, find_struct_impl, generate_impl_text}, | 6 | utils::{add_method_to_adt, find_struct_impl}, |
8 | AssistContext, AssistId, AssistKind, Assists, | 7 | AssistContext, AssistId, AssistKind, Assists, |
9 | }; | 8 | }; |
10 | 9 | ||
11 | // Assist: generate_enum_match_method | 10 | // Assist: generate_enum_is_method |
12 | // | 11 | // |
13 | // Generate an `is_` method for an enum variant. | 12 | // Generate an `is_` method for an enum variant. |
14 | // | 13 | // |
@@ -34,79 +33,52 @@ use crate::{ | |||
34 | // } | 33 | // } |
35 | // } | 34 | // } |
36 | // ``` | 35 | // ``` |
37 | pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 36 | pub(crate) fn generate_enum_is_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
38 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | 37 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; |
39 | let variant_name = variant.name()?; | 38 | let variant_name = variant.name()?; |
40 | let parent_enum = variant.parent_enum(); | 39 | let parent_enum = ast::Adt::Enum(variant.parent_enum()); |
41 | if !matches!(variant.kind(), ast::StructKind::Unit) { | 40 | let pattern_suffix = match variant.kind() { |
42 | mark::hit!(test_gen_enum_match_on_non_unit_variant_not_implemented); | 41 | ast::StructKind::Record(_) => " { .. }", |
43 | return None; | 42 | ast::StructKind::Tuple(_) => "(..)", |
44 | } | 43 | ast::StructKind::Unit => "", |
44 | }; | ||
45 | 45 | ||
46 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); | 46 | let enum_lowercase_name = to_lower_snake_case(&parent_enum.name()?.to_string()); |
47 | let fn_name = to_lower_snake_case(&variant_name.to_string()); | 47 | let fn_name = format!("is_{}", &to_lower_snake_case(variant_name.text())); |
48 | 48 | ||
49 | // Return early if we've found an existing new fn | 49 | // Return early if we've found an existing new fn |
50 | let impl_def = find_struct_impl( | 50 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; |
51 | &ctx, | ||
52 | &ast::Adt::Enum(parent_enum.clone()), | ||
53 | format!("is_{}", fn_name).as_str(), | ||
54 | )?; | ||
55 | 51 | ||
56 | let target = variant.syntax().text_range(); | 52 | let target = variant.syntax().text_range(); |
57 | acc.add( | 53 | acc.add( |
58 | AssistId("generate_enum_match_method", AssistKind::Generate), | 54 | AssistId("generate_enum_is_method", AssistKind::Generate), |
59 | "Generate an `is_` method for an enum variant", | 55 | "Generate an `is_` method for an enum variant", |
60 | target, | 56 | target, |
61 | |builder| { | 57 | |builder| { |
62 | let mut buf = String::with_capacity(512); | ||
63 | |||
64 | if impl_def.is_some() { | ||
65 | buf.push('\n'); | ||
66 | } | ||
67 | |||
68 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); | 58 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); |
69 | format_to!( | 59 | let method = format!( |
70 | buf, | ||
71 | " /// Returns `true` if the {} is [`{}`]. | 60 | " /// Returns `true` if the {} is [`{}`]. |
72 | {}fn is_{}(&self) -> bool {{ | 61 | {}fn {}(&self) -> bool {{ |
73 | matches!(self, Self::{}) | 62 | matches!(self, Self::{}{}) |
74 | }}", | 63 | }}", |
75 | enum_lowercase_name, | 64 | enum_lowercase_name, variant_name, vis, fn_name, variant_name, pattern_suffix, |
76 | variant_name, | ||
77 | vis, | ||
78 | fn_name, | ||
79 | variant_name | ||
80 | ); | 65 | ); |
81 | 66 | ||
82 | let start_offset = impl_def | 67 | add_method_to_adt(builder, &parent_enum, impl_def, &method); |
83 | .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) | ||
84 | .unwrap_or_else(|| { | ||
85 | buf = generate_impl_text(&ast::Adt::Enum(parent_enum.clone()), &buf); | ||
86 | parent_enum.syntax().text_range().end() | ||
87 | }); | ||
88 | |||
89 | builder.insert(start_offset, buf); | ||
90 | }, | 68 | }, |
91 | ) | 69 | ) |
92 | } | 70 | } |
93 | 71 | ||
94 | #[cfg(test)] | 72 | #[cfg(test)] |
95 | mod tests { | 73 | mod tests { |
96 | use test_utils::mark; | ||
97 | |||
98 | use crate::tests::{check_assist, check_assist_not_applicable}; | 74 | use crate::tests::{check_assist, check_assist_not_applicable}; |
99 | 75 | ||
100 | use super::*; | 76 | use super::*; |
101 | 77 | ||
102 | fn check_not_applicable(ra_fixture: &str) { | ||
103 | check_assist_not_applicable(generate_enum_match_method, ra_fixture) | ||
104 | } | ||
105 | |||
106 | #[test] | 78 | #[test] |
107 | fn test_generate_enum_match_from_variant() { | 79 | fn test_generate_enum_is_from_variant() { |
108 | check_assist( | 80 | check_assist( |
109 | generate_enum_match_method, | 81 | generate_enum_is_method, |
110 | r#" | 82 | r#" |
111 | enum Variant { | 83 | enum Variant { |
112 | Undefined, | 84 | Undefined, |
@@ -129,8 +101,9 @@ impl Variant { | |||
129 | } | 101 | } |
130 | 102 | ||
131 | #[test] | 103 | #[test] |
132 | fn test_generate_enum_match_already_implemented() { | 104 | fn test_generate_enum_is_already_implemented() { |
133 | check_not_applicable( | 105 | check_assist_not_applicable( |
106 | generate_enum_is_method, | ||
134 | r#" | 107 | r#" |
135 | enum Variant { | 108 | enum Variant { |
136 | Undefined, | 109 | Undefined, |
@@ -147,22 +120,59 @@ impl Variant { | |||
147 | } | 120 | } |
148 | 121 | ||
149 | #[test] | 122 | #[test] |
150 | fn test_add_from_impl_no_element() { | 123 | fn test_generate_enum_is_from_tuple_variant() { |
151 | mark::check!(test_gen_enum_match_on_non_unit_variant_not_implemented); | 124 | check_assist( |
152 | check_not_applicable( | 125 | generate_enum_is_method, |
153 | r#" | 126 | r#" |
154 | enum Variant { | 127 | enum Variant { |
155 | Undefined, | 128 | Undefined, |
156 | Minor(u32)$0, | 129 | Minor(u32)$0, |
157 | Major, | 130 | Major, |
158 | }"#, | 131 | }"#, |
132 | r#"enum Variant { | ||
133 | Undefined, | ||
134 | Minor(u32), | ||
135 | Major, | ||
136 | } | ||
137 | |||
138 | impl Variant { | ||
139 | /// Returns `true` if the variant is [`Minor`]. | ||
140 | fn is_minor(&self) -> bool { | ||
141 | matches!(self, Self::Minor(..)) | ||
142 | } | ||
143 | }"#, | ||
144 | ); | ||
145 | } | ||
146 | |||
147 | #[test] | ||
148 | fn test_generate_enum_is_from_record_variant() { | ||
149 | check_assist( | ||
150 | generate_enum_is_method, | ||
151 | r#" | ||
152 | enum Variant { | ||
153 | Undefined, | ||
154 | Minor { foo: i32 }$0, | ||
155 | Major, | ||
156 | }"#, | ||
157 | r#"enum Variant { | ||
158 | Undefined, | ||
159 | Minor { foo: i32 }, | ||
160 | Major, | ||
161 | } | ||
162 | |||
163 | impl Variant { | ||
164 | /// Returns `true` if the variant is [`Minor`]. | ||
165 | fn is_minor(&self) -> bool { | ||
166 | matches!(self, Self::Minor { .. }) | ||
167 | } | ||
168 | }"#, | ||
159 | ); | 169 | ); |
160 | } | 170 | } |
161 | 171 | ||
162 | #[test] | 172 | #[test] |
163 | fn test_generate_enum_match_from_variant_with_one_variant() { | 173 | fn test_generate_enum_is_from_variant_with_one_variant() { |
164 | check_assist( | 174 | check_assist( |
165 | generate_enum_match_method, | 175 | generate_enum_is_method, |
166 | r#"enum Variant { Undefi$0ned }"#, | 176 | r#"enum Variant { Undefi$0ned }"#, |
167 | r#" | 177 | r#" |
168 | enum Variant { Undefined } | 178 | enum Variant { Undefined } |
@@ -177,9 +187,9 @@ impl Variant { | |||
177 | } | 187 | } |
178 | 188 | ||
179 | #[test] | 189 | #[test] |
180 | fn test_generate_enum_match_from_variant_with_visibility_marker() { | 190 | fn test_generate_enum_is_from_variant_with_visibility_marker() { |
181 | check_assist( | 191 | check_assist( |
182 | generate_enum_match_method, | 192 | generate_enum_is_method, |
183 | r#" | 193 | r#" |
184 | pub(crate) enum Variant { | 194 | pub(crate) enum Variant { |
185 | Undefined, | 195 | Undefined, |
@@ -202,9 +212,9 @@ impl Variant { | |||
202 | } | 212 | } |
203 | 213 | ||
204 | #[test] | 214 | #[test] |
205 | fn test_multiple_generate_enum_match_from_variant() { | 215 | fn test_multiple_generate_enum_is_from_variant() { |
206 | check_assist( | 216 | check_assist( |
207 | generate_enum_match_method, | 217 | generate_enum_is_method, |
208 | r#" | 218 | r#" |
209 | enum Variant { | 219 | enum Variant { |
210 | Undefined, | 220 | Undefined, |
diff --git a/crates/ide_assists/src/handlers/generate_enum_projection_method.rs b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs new file mode 100644 index 000000000..871bcab50 --- /dev/null +++ b/crates/ide_assists/src/handlers/generate_enum_projection_method.rs | |||
@@ -0,0 +1,331 @@ | |||
1 | use itertools::Itertools; | ||
2 | use stdx::to_lower_snake_case; | ||
3 | use syntax::ast::VisibilityOwner; | ||
4 | use syntax::ast::{self, AstNode, NameOwner}; | ||
5 | |||
6 | use crate::{ | ||
7 | utils::{add_method_to_adt, find_struct_impl}, | ||
8 | AssistContext, AssistId, AssistKind, Assists, | ||
9 | }; | ||
10 | |||
11 | // Assist: generate_enum_try_into_method | ||
12 | // | ||
13 | // Generate an `try_into_` method for an enum variant. | ||
14 | // | ||
15 | // ``` | ||
16 | // enum Value { | ||
17 | // Number(i32), | ||
18 | // Text(String)$0, | ||
19 | // } | ||
20 | // ``` | ||
21 | // -> | ||
22 | // ``` | ||
23 | // enum Value { | ||
24 | // Number(i32), | ||
25 | // Text(String), | ||
26 | // } | ||
27 | // | ||
28 | // impl Value { | ||
29 | // fn try_into_text(self) -> Result<String, Self> { | ||
30 | // if let Self::Text(v) = self { | ||
31 | // Ok(v) | ||
32 | // } else { | ||
33 | // Err(self) | ||
34 | // } | ||
35 | // } | ||
36 | // } | ||
37 | // ``` | ||
38 | pub(crate) fn generate_enum_try_into_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
39 | generate_enum_projection_method( | ||
40 | acc, | ||
41 | ctx, | ||
42 | "generate_enum_try_into_method", | ||
43 | "Generate an `try_into_` method for an enum variant", | ||
44 | ProjectionProps { | ||
45 | fn_name_prefix: "try_into", | ||
46 | self_param: "self", | ||
47 | return_prefix: "Result<", | ||
48 | return_suffix: ", Self>", | ||
49 | happy_case: "Ok", | ||
50 | sad_case: "Err(self)", | ||
51 | }, | ||
52 | ) | ||
53 | } | ||
54 | |||
55 | // Assist: generate_enum_as_method | ||
56 | // | ||
57 | // Generate an `as_` method for an enum variant. | ||
58 | // | ||
59 | // ``` | ||
60 | // enum Value { | ||
61 | // Number(i32), | ||
62 | // Text(String)$0, | ||
63 | // } | ||
64 | // ``` | ||
65 | // -> | ||
66 | // ``` | ||
67 | // enum Value { | ||
68 | // Number(i32), | ||
69 | // Text(String), | ||
70 | // } | ||
71 | // | ||
72 | // impl Value { | ||
73 | // fn as_text(&self) -> Option<&String> { | ||
74 | // if let Self::Text(v) = self { | ||
75 | // Some(v) | ||
76 | // } else { | ||
77 | // None | ||
78 | // } | ||
79 | // } | ||
80 | // } | ||
81 | // ``` | ||
82 | pub(crate) fn generate_enum_as_method(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
83 | generate_enum_projection_method( | ||
84 | acc, | ||
85 | ctx, | ||
86 | "generate_enum_as_method", | ||
87 | "Generate an `as_` method for an enum variant", | ||
88 | ProjectionProps { | ||
89 | fn_name_prefix: "as", | ||
90 | self_param: "&self", | ||
91 | return_prefix: "Option<&", | ||
92 | return_suffix: ">", | ||
93 | happy_case: "Some", | ||
94 | sad_case: "None", | ||
95 | }, | ||
96 | ) | ||
97 | } | ||
98 | |||
99 | struct ProjectionProps { | ||
100 | fn_name_prefix: &'static str, | ||
101 | self_param: &'static str, | ||
102 | return_prefix: &'static str, | ||
103 | return_suffix: &'static str, | ||
104 | happy_case: &'static str, | ||
105 | sad_case: &'static str, | ||
106 | } | ||
107 | |||
108 | fn generate_enum_projection_method( | ||
109 | acc: &mut Assists, | ||
110 | ctx: &AssistContext, | ||
111 | assist_id: &'static str, | ||
112 | assist_description: &str, | ||
113 | props: ProjectionProps, | ||
114 | ) -> Option<()> { | ||
115 | let variant = ctx.find_node_at_offset::<ast::Variant>()?; | ||
116 | let variant_name = variant.name()?; | ||
117 | let parent_enum = ast::Adt::Enum(variant.parent_enum()); | ||
118 | |||
119 | let (pattern_suffix, field_type, bound_name) = match variant.kind() { | ||
120 | ast::StructKind::Record(record) => { | ||
121 | let (field,) = record.fields().collect_tuple()?; | ||
122 | let name = field.name()?.to_string(); | ||
123 | let ty = field.ty()?; | ||
124 | let pattern_suffix = format!(" {{ {} }}", name); | ||
125 | (pattern_suffix, ty, name) | ||
126 | } | ||
127 | ast::StructKind::Tuple(tuple) => { | ||
128 | let (field,) = tuple.fields().collect_tuple()?; | ||
129 | let ty = field.ty()?; | ||
130 | ("(v)".to_owned(), ty, "v".to_owned()) | ||
131 | } | ||
132 | ast::StructKind::Unit => return None, | ||
133 | }; | ||
134 | |||
135 | let fn_name = format!("{}_{}", props.fn_name_prefix, &to_lower_snake_case(variant_name.text())); | ||
136 | |||
137 | // Return early if we've found an existing new fn | ||
138 | let impl_def = find_struct_impl(&ctx, &parent_enum, &fn_name)?; | ||
139 | |||
140 | let target = variant.syntax().text_range(); | ||
141 | acc.add(AssistId(assist_id, AssistKind::Generate), assist_description, target, |builder| { | ||
142 | let vis = parent_enum.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
143 | let method = format!( | ||
144 | " {0}fn {1}({2}) -> {3}{4}{5} {{ | ||
145 | if let Self::{6}{7} = self {{ | ||
146 | {8}({9}) | ||
147 | }} else {{ | ||
148 | {10} | ||
149 | }} | ||
150 | }}", | ||
151 | vis, | ||
152 | fn_name, | ||
153 | props.self_param, | ||
154 | props.return_prefix, | ||
155 | field_type.syntax(), | ||
156 | props.return_suffix, | ||
157 | variant_name, | ||
158 | pattern_suffix, | ||
159 | props.happy_case, | ||
160 | bound_name, | ||
161 | props.sad_case, | ||
162 | ); | ||
163 | |||
164 | add_method_to_adt(builder, &parent_enum, impl_def, &method); | ||
165 | }) | ||
166 | } | ||
167 | |||
168 | #[cfg(test)] | ||
169 | mod tests { | ||
170 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
171 | |||
172 | use super::*; | ||
173 | |||
174 | #[test] | ||
175 | fn test_generate_enum_try_into_tuple_variant() { | ||
176 | check_assist( | ||
177 | generate_enum_try_into_method, | ||
178 | r#" | ||
179 | enum Value { | ||
180 | Number(i32), | ||
181 | Text(String)$0, | ||
182 | }"#, | ||
183 | r#"enum Value { | ||
184 | Number(i32), | ||
185 | Text(String), | ||
186 | } | ||
187 | |||
188 | impl Value { | ||
189 | fn try_into_text(self) -> Result<String, Self> { | ||
190 | if let Self::Text(v) = self { | ||
191 | Ok(v) | ||
192 | } else { | ||
193 | Err(self) | ||
194 | } | ||
195 | } | ||
196 | }"#, | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
201 | fn test_generate_enum_try_into_already_implemented() { | ||
202 | check_assist_not_applicable( | ||
203 | generate_enum_try_into_method, | ||
204 | r#"enum Value { | ||
205 | Number(i32), | ||
206 | Text(String)$0, | ||
207 | } | ||
208 | |||
209 | impl Value { | ||
210 | fn try_into_text(self) -> Result<String, Self> { | ||
211 | if let Self::Text(v) = self { | ||
212 | Ok(v) | ||
213 | } else { | ||
214 | Err(self) | ||
215 | } | ||
216 | } | ||
217 | }"#, | ||
218 | ); | ||
219 | } | ||
220 | |||
221 | #[test] | ||
222 | fn test_generate_enum_try_into_unit_variant() { | ||
223 | check_assist_not_applicable( | ||
224 | generate_enum_try_into_method, | ||
225 | r#"enum Value { | ||
226 | Number(i32), | ||
227 | Text(String), | ||
228 | Unit$0, | ||
229 | }"#, | ||
230 | ); | ||
231 | } | ||
232 | |||
233 | #[test] | ||
234 | fn test_generate_enum_try_into_record_with_multiple_fields() { | ||
235 | check_assist_not_applicable( | ||
236 | generate_enum_try_into_method, | ||
237 | r#"enum Value { | ||
238 | Number(i32), | ||
239 | Text(String), | ||
240 | Both { first: i32, second: String }$0, | ||
241 | }"#, | ||
242 | ); | ||
243 | } | ||
244 | |||
245 | #[test] | ||
246 | fn test_generate_enum_try_into_tuple_with_multiple_fields() { | ||
247 | check_assist_not_applicable( | ||
248 | generate_enum_try_into_method, | ||
249 | r#"enum Value { | ||
250 | Number(i32), | ||
251 | Text(String, String)$0, | ||
252 | }"#, | ||
253 | ); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn test_generate_enum_try_into_record_variant() { | ||
258 | check_assist( | ||
259 | generate_enum_try_into_method, | ||
260 | r#"enum Value { | ||
261 | Number(i32), | ||
262 | Text { text: String }$0, | ||
263 | }"#, | ||
264 | r#"enum Value { | ||
265 | Number(i32), | ||
266 | Text { text: String }, | ||
267 | } | ||
268 | |||
269 | impl Value { | ||
270 | fn try_into_text(self) -> Result<String, Self> { | ||
271 | if let Self::Text { text } = self { | ||
272 | Ok(text) | ||
273 | } else { | ||
274 | Err(self) | ||
275 | } | ||
276 | } | ||
277 | }"#, | ||
278 | ); | ||
279 | } | ||
280 | |||
281 | #[test] | ||
282 | fn test_generate_enum_as_tuple_variant() { | ||
283 | check_assist( | ||
284 | generate_enum_as_method, | ||
285 | r#" | ||
286 | enum Value { | ||
287 | Number(i32), | ||
288 | Text(String)$0, | ||
289 | }"#, | ||
290 | r#"enum Value { | ||
291 | Number(i32), | ||
292 | Text(String), | ||
293 | } | ||
294 | |||
295 | impl Value { | ||
296 | fn as_text(&self) -> Option<&String> { | ||
297 | if let Self::Text(v) = self { | ||
298 | Some(v) | ||
299 | } else { | ||
300 | None | ||
301 | } | ||
302 | } | ||
303 | }"#, | ||
304 | ); | ||
305 | } | ||
306 | |||
307 | #[test] | ||
308 | fn test_generate_enum_as_record_variant() { | ||
309 | check_assist( | ||
310 | generate_enum_as_method, | ||
311 | r#"enum Value { | ||
312 | Number(i32), | ||
313 | Text { text: String }$0, | ||
314 | }"#, | ||
315 | r#"enum Value { | ||
316 | Number(i32), | ||
317 | Text { text: String }, | ||
318 | } | ||
319 | |||
320 | impl Value { | ||
321 | fn as_text(&self) -> Option<&String> { | ||
322 | if let Self::Text { text } = self { | ||
323 | Some(text) | ||
324 | } else { | ||
325 | None | ||
326 | } | ||
327 | } | ||
328 | }"#, | ||
329 | ); | ||
330 | } | ||
331 | } | ||
diff --git a/crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs b/crates/ide_assists/src/handlers/replace_for_loop_with_for_each.rs index 9fddf889c..27da28bc0 100644 --- a/crates/ide_assists/src/handlers/convert_for_to_iter_for_each.rs +++ b/crates/ide_assists/src/handlers/replace_for_loop_with_for_each.rs | |||
@@ -3,17 +3,18 @@ use hir::known; | |||
3 | use ide_db::helpers::FamousDefs; | 3 | use ide_db::helpers::FamousDefs; |
4 | use stdx::format_to; | 4 | use stdx::format_to; |
5 | use syntax::{ast, AstNode}; | 5 | use syntax::{ast, AstNode}; |
6 | use test_utils::mark; | ||
6 | 7 | ||
7 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 8 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
8 | 9 | ||
9 | // Assist: convert_for_to_iter_for_each | 10 | // Assist: replace_for_loop_with_for_each |
10 | // | 11 | // |
11 | // Converts a for loop into a for_each loop on the Iterator. | 12 | // Converts a for loop into a for_each loop on the Iterator. |
12 | // | 13 | // |
13 | // ``` | 14 | // ``` |
14 | // fn main() { | 15 | // fn main() { |
15 | // let x = vec![1, 2, 3]; | 16 | // let x = vec![1, 2, 3]; |
16 | // for $0v in x { | 17 | // for$0 v in x { |
17 | // let y = v * 2; | 18 | // let y = v * 2; |
18 | // } | 19 | // } |
19 | // } | 20 | // } |
@@ -27,15 +28,19 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; | |||
27 | // }); | 28 | // }); |
28 | // } | 29 | // } |
29 | // ``` | 30 | // ``` |
30 | pub(crate) fn convert_for_to_iter_for_each(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | pub(crate) fn replace_for_loop_with_for_each(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
31 | let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?; | 32 | let for_loop = ctx.find_node_at_offset::<ast::ForExpr>()?; |
32 | let iterable = for_loop.iterable()?; | 33 | let iterable = for_loop.iterable()?; |
33 | let pat = for_loop.pat()?; | 34 | let pat = for_loop.pat()?; |
34 | let body = for_loop.loop_body()?; | 35 | let body = for_loop.loop_body()?; |
36 | if body.syntax().text_range().start() < ctx.offset() { | ||
37 | mark::hit!(not_available_in_body); | ||
38 | return None; | ||
39 | } | ||
35 | 40 | ||
36 | acc.add( | 41 | acc.add( |
37 | AssistId("convert_for_to_iter_for_each", AssistKind::RefactorRewrite), | 42 | AssistId("replace_for_loop_with_for_each", AssistKind::RefactorRewrite), |
38 | "Convert a for loop into an Iterator::for_each", | 43 | "Replace this for loop with `Iterator::for_each`", |
39 | for_loop.syntax().text_range(), | 44 | for_loop.syntax().text_range(), |
40 | |builder| { | 45 | |builder| { |
41 | let mut buf = String::new(); | 46 | let mut buf = String::new(); |
@@ -145,13 +150,13 @@ pub struct NoIterMethod; | |||
145 | FamousDefs::FIXTURE, | 150 | FamousDefs::FIXTURE, |
146 | EMPTY_ITER_FIXTURE | 151 | EMPTY_ITER_FIXTURE |
147 | ); | 152 | ); |
148 | check_assist(convert_for_to_iter_for_each, before, after); | 153 | check_assist(replace_for_loop_with_for_each, before, after); |
149 | } | 154 | } |
150 | 155 | ||
151 | #[test] | 156 | #[test] |
152 | fn test_not_for() { | 157 | fn test_not_for() { |
153 | check_assist_not_applicable( | 158 | check_assist_not_applicable( |
154 | convert_for_to_iter_for_each, | 159 | replace_for_loop_with_for_each, |
155 | r" | 160 | r" |
156 | let mut x = vec![1, 2, 3]; | 161 | let mut x = vec![1, 2, 3]; |
157 | x.iter_mut().$0for_each(|v| *v *= 2); | 162 | x.iter_mut().$0for_each(|v| *v *= 2); |
@@ -162,7 +167,7 @@ x.iter_mut().$0for_each(|v| *v *= 2); | |||
162 | #[test] | 167 | #[test] |
163 | fn test_simple_for() { | 168 | fn test_simple_for() { |
164 | check_assist( | 169 | check_assist( |
165 | convert_for_to_iter_for_each, | 170 | replace_for_loop_with_for_each, |
166 | r" | 171 | r" |
167 | fn main() { | 172 | fn main() { |
168 | let x = vec![1, 2, 3]; | 173 | let x = vec![1, 2, 3]; |
@@ -181,6 +186,21 @@ fn main() { | |||
181 | } | 186 | } |
182 | 187 | ||
183 | #[test] | 188 | #[test] |
189 | fn not_available_in_body() { | ||
190 | mark::check!(not_available_in_body); | ||
191 | check_assist_not_applicable( | ||
192 | replace_for_loop_with_for_each, | ||
193 | r" | ||
194 | fn main() { | ||
195 | let x = vec![1, 2, 3]; | ||
196 | for v in x { | ||
197 | $0v *= 2; | ||
198 | } | ||
199 | }", | ||
200 | ) | ||
201 | } | ||
202 | |||
203 | #[test] | ||
184 | fn test_for_borrowed() { | 204 | fn test_for_borrowed() { |
185 | check_assist_with_fixtures( | 205 | check_assist_with_fixtures( |
186 | r" | 206 | r" |
@@ -255,7 +275,7 @@ fn main() { | |||
255 | #[test] | 275 | #[test] |
256 | fn test_for_borrowed_mut_behind_var() { | 276 | fn test_for_borrowed_mut_behind_var() { |
257 | check_assist( | 277 | check_assist( |
258 | convert_for_to_iter_for_each, | 278 | replace_for_loop_with_for_each, |
259 | r" | 279 | r" |
260 | fn main() { | 280 | fn main() { |
261 | let x = vec![1, 2, 3]; | 281 | let x = vec![1, 2, 3]; |
diff --git a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs index 5a27ada6b..be7e724b5 100644 --- a/crates/ide_assists/src/handlers/replace_let_with_if_let.rs +++ b/crates/ide_assists/src/handlers/replace_let_with_if_let.rs | |||
@@ -1,5 +1,6 @@ | |||
1 | use std::iter::once; | 1 | use std::iter::once; |
2 | 2 | ||
3 | use ide_db::ty_filter::TryEnum; | ||
3 | use syntax::{ | 4 | use syntax::{ |
4 | ast::{ | 5 | ast::{ |
5 | self, | 6 | self, |
@@ -10,7 +11,6 @@ use syntax::{ | |||
10 | }; | 11 | }; |
11 | 12 | ||
12 | use crate::{AssistContext, AssistId, AssistKind, Assists}; | 13 | use crate::{AssistContext, AssistId, AssistKind, Assists}; |
13 | use ide_db::ty_filter::TryEnum; | ||
14 | 14 | ||
15 | // Assist: replace_let_with_if_let | 15 | // Assist: replace_let_with_if_let |
16 | // | 16 | // |
diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index f4c7e6fbf..53542d433 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs | |||
@@ -114,7 +114,6 @@ mod handlers { | |||
114 | mod apply_demorgan; | 114 | mod apply_demorgan; |
115 | mod auto_import; | 115 | mod auto_import; |
116 | mod change_visibility; | 116 | mod change_visibility; |
117 | mod convert_for_to_iter_for_each; | ||
118 | mod convert_integer_literal; | 117 | mod convert_integer_literal; |
119 | mod early_return; | 118 | mod early_return; |
120 | mod expand_glob_import; | 119 | mod expand_glob_import; |
@@ -128,11 +127,12 @@ mod handlers { | |||
128 | mod flip_trait_bound; | 127 | mod flip_trait_bound; |
129 | mod generate_default_from_enum_variant; | 128 | mod generate_default_from_enum_variant; |
130 | mod generate_derive; | 129 | mod generate_derive; |
131 | mod generate_enum_match_method; | 130 | mod generate_enum_is_method; |
131 | mod generate_enum_projection_method; | ||
132 | mod generate_from_impl_for_enum; | 132 | mod generate_from_impl_for_enum; |
133 | mod generate_function; | 133 | mod generate_function; |
134 | mod generate_getter; | ||
135 | mod generate_getter_mut; | 134 | mod generate_getter_mut; |
135 | mod generate_getter; | ||
136 | mod generate_impl; | 136 | mod generate_impl; |
137 | mod generate_new; | 137 | mod generate_new; |
138 | mod generate_setter; | 138 | mod generate_setter; |
@@ -155,6 +155,7 @@ mod handlers { | |||
155 | mod reorder_fields; | 155 | mod reorder_fields; |
156 | mod reorder_impl; | 156 | mod reorder_impl; |
157 | mod replace_derive_with_manual_impl; | 157 | mod replace_derive_with_manual_impl; |
158 | mod replace_for_loop_with_for_each; | ||
158 | mod replace_if_let_with_match; | 159 | mod replace_if_let_with_match; |
159 | mod replace_impl_trait_with_generic; | 160 | mod replace_impl_trait_with_generic; |
160 | mod replace_let_with_if_let; | 161 | mod replace_let_with_if_let; |
@@ -176,11 +177,9 @@ mod handlers { | |||
176 | apply_demorgan::apply_demorgan, | 177 | apply_demorgan::apply_demorgan, |
177 | auto_import::auto_import, | 178 | auto_import::auto_import, |
178 | change_visibility::change_visibility, | 179 | change_visibility::change_visibility, |
179 | convert_for_to_iter_for_each::convert_for_to_iter_for_each, | ||
180 | convert_integer_literal::convert_integer_literal, | 180 | convert_integer_literal::convert_integer_literal, |
181 | early_return::convert_to_guarded_return, | 181 | early_return::convert_to_guarded_return, |
182 | expand_glob_import::expand_glob_import, | 182 | expand_glob_import::expand_glob_import, |
183 | move_module_to_file::move_module_to_file, | ||
184 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, | 183 | extract_struct_from_enum_variant::extract_struct_from_enum_variant, |
185 | fill_match_arms::fill_match_arms, | 184 | fill_match_arms::fill_match_arms, |
186 | fix_visibility::fix_visibility, | 185 | fix_visibility::fix_visibility, |
@@ -189,11 +188,13 @@ mod handlers { | |||
189 | flip_trait_bound::flip_trait_bound, | 188 | flip_trait_bound::flip_trait_bound, |
190 | generate_default_from_enum_variant::generate_default_from_enum_variant, | 189 | generate_default_from_enum_variant::generate_default_from_enum_variant, |
191 | generate_derive::generate_derive, | 190 | generate_derive::generate_derive, |
192 | generate_enum_match_method::generate_enum_match_method, | 191 | generate_enum_is_method::generate_enum_is_method, |
192 | generate_enum_projection_method::generate_enum_as_method, | ||
193 | generate_enum_projection_method::generate_enum_try_into_method, | ||
193 | generate_from_impl_for_enum::generate_from_impl_for_enum, | 194 | generate_from_impl_for_enum::generate_from_impl_for_enum, |
194 | generate_function::generate_function, | 195 | generate_function::generate_function, |
195 | generate_getter::generate_getter, | ||
196 | generate_getter_mut::generate_getter_mut, | 196 | generate_getter_mut::generate_getter_mut, |
197 | generate_getter::generate_getter, | ||
197 | generate_impl::generate_impl, | 198 | generate_impl::generate_impl, |
198 | generate_new::generate_new, | 199 | generate_new::generate_new, |
199 | generate_setter::generate_setter, | 200 | generate_setter::generate_setter, |
@@ -207,6 +208,7 @@ mod handlers { | |||
207 | move_bounds::move_bounds_to_where_clause, | 208 | move_bounds::move_bounds_to_where_clause, |
208 | move_guard::move_arm_cond_to_match_guard, | 209 | move_guard::move_arm_cond_to_match_guard, |
209 | move_guard::move_guard_to_arm_body, | 210 | move_guard::move_guard_to_arm_body, |
211 | move_module_to_file::move_module_to_file, | ||
210 | pull_assignment_up::pull_assignment_up, | 212 | pull_assignment_up::pull_assignment_up, |
211 | qualify_path::qualify_path, | 213 | qualify_path::qualify_path, |
212 | raw_string::add_hash, | 214 | raw_string::add_hash, |
@@ -218,6 +220,7 @@ mod handlers { | |||
218 | reorder_fields::reorder_fields, | 220 | reorder_fields::reorder_fields, |
219 | reorder_impl::reorder_impl, | 221 | reorder_impl::reorder_impl, |
220 | replace_derive_with_manual_impl::replace_derive_with_manual_impl, | 222 | replace_derive_with_manual_impl::replace_derive_with_manual_impl, |
223 | replace_for_loop_with_for_each::replace_for_loop_with_for_each, | ||
221 | replace_if_let_with_match::replace_if_let_with_match, | 224 | replace_if_let_with_match::replace_if_let_with_match, |
222 | replace_if_let_with_match::replace_match_with_if_let, | 225 | replace_if_let_with_match::replace_match_with_if_let, |
223 | replace_impl_trait_with_generic::replace_impl_trait_with_generic, | 226 | replace_impl_trait_with_generic::replace_impl_trait_with_generic, |
diff --git a/crates/ide_assists/src/tests.rs b/crates/ide_assists/src/tests.rs index 384eb7eee..b7f616760 100644 --- a/crates/ide_assists/src/tests.rs +++ b/crates/ide_assists/src/tests.rs | |||
@@ -190,8 +190,8 @@ fn assist_order_field_struct() { | |||
190 | let mut assists = assists.iter(); | 190 | let mut assists = assists.iter(); |
191 | 191 | ||
192 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); | 192 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
193 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); | ||
194 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); | 193 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); |
194 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); | ||
195 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); | 195 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); |
196 | assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); | 196 | assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); |
197 | } | 197 | } |
diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index d42875822..4f007aa48 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs | |||
@@ -193,29 +193,6 @@ pub(crate) fn frobnicate() {} | |||
193 | } | 193 | } |
194 | 194 | ||
195 | #[test] | 195 | #[test] |
196 | fn doctest_convert_for_to_iter_for_each() { | ||
197 | check_doc_test( | ||
198 | "convert_for_to_iter_for_each", | ||
199 | r#####" | ||
200 | fn main() { | ||
201 | let x = vec![1, 2, 3]; | ||
202 | for $0v in x { | ||
203 | let y = v * 2; | ||
204 | } | ||
205 | } | ||
206 | "#####, | ||
207 | r#####" | ||
208 | fn main() { | ||
209 | let x = vec![1, 2, 3]; | ||
210 | x.into_iter().for_each(|v| { | ||
211 | let y = v * 2; | ||
212 | }); | ||
213 | } | ||
214 | "#####, | ||
215 | ) | ||
216 | } | ||
217 | |||
218 | #[test] | ||
219 | fn doctest_convert_integer_literal() { | 196 | fn doctest_convert_integer_literal() { |
220 | check_doc_test( | 197 | check_doc_test( |
221 | "convert_integer_literal", | 198 | "convert_integer_literal", |
@@ -483,9 +460,38 @@ struct Point { | |||
483 | } | 460 | } |
484 | 461 | ||
485 | #[test] | 462 | #[test] |
486 | fn doctest_generate_enum_match_method() { | 463 | fn doctest_generate_enum_as_method() { |
464 | check_doc_test( | ||
465 | "generate_enum_as_method", | ||
466 | r#####" | ||
467 | enum Value { | ||
468 | Number(i32), | ||
469 | Text(String)$0, | ||
470 | } | ||
471 | "#####, | ||
472 | r#####" | ||
473 | enum Value { | ||
474 | Number(i32), | ||
475 | Text(String), | ||
476 | } | ||
477 | |||
478 | impl Value { | ||
479 | fn as_text(&self) -> Option<&String> { | ||
480 | if let Self::Text(v) = self { | ||
481 | Some(v) | ||
482 | } else { | ||
483 | None | ||
484 | } | ||
485 | } | ||
486 | } | ||
487 | "#####, | ||
488 | ) | ||
489 | } | ||
490 | |||
491 | #[test] | ||
492 | fn doctest_generate_enum_is_method() { | ||
487 | check_doc_test( | 493 | check_doc_test( |
488 | "generate_enum_match_method", | 494 | "generate_enum_is_method", |
489 | r#####" | 495 | r#####" |
490 | enum Version { | 496 | enum Version { |
491 | Undefined, | 497 | Undefined, |
@@ -511,6 +517,35 @@ impl Version { | |||
511 | } | 517 | } |
512 | 518 | ||
513 | #[test] | 519 | #[test] |
520 | fn doctest_generate_enum_try_into_method() { | ||
521 | check_doc_test( | ||
522 | "generate_enum_try_into_method", | ||
523 | r#####" | ||
524 | enum Value { | ||
525 | Number(i32), | ||
526 | Text(String)$0, | ||
527 | } | ||
528 | "#####, | ||
529 | r#####" | ||
530 | enum Value { | ||
531 | Number(i32), | ||
532 | Text(String), | ||
533 | } | ||
534 | |||
535 | impl Value { | ||
536 | fn try_into_text(self) -> Result<String, Self> { | ||
537 | if let Self::Text(v) = self { | ||
538 | Ok(v) | ||
539 | } else { | ||
540 | Err(self) | ||
541 | } | ||
542 | } | ||
543 | } | ||
544 | "#####, | ||
545 | ) | ||
546 | } | ||
547 | |||
548 | #[test] | ||
514 | fn doctest_generate_from_impl_for_enum() { | 549 | fn doctest_generate_from_impl_for_enum() { |
515 | check_doc_test( | 550 | check_doc_test( |
516 | "generate_from_impl_for_enum", | 551 | "generate_from_impl_for_enum", |
@@ -1122,6 +1157,29 @@ impl Debug for S { | |||
1122 | } | 1157 | } |
1123 | 1158 | ||
1124 | #[test] | 1159 | #[test] |
1160 | fn doctest_replace_for_loop_with_for_each() { | ||
1161 | check_doc_test( | ||
1162 | "replace_for_loop_with_for_each", | ||
1163 | r#####" | ||
1164 | fn main() { | ||
1165 | let x = vec![1, 2, 3]; | ||
1166 | for$0 v in x { | ||
1167 | let y = v * 2; | ||
1168 | } | ||
1169 | } | ||
1170 | "#####, | ||
1171 | r#####" | ||
1172 | fn main() { | ||
1173 | let x = vec![1, 2, 3]; | ||
1174 | x.into_iter().for_each(|v| { | ||
1175 | let y = v * 2; | ||
1176 | }); | ||
1177 | } | ||
1178 | "#####, | ||
1179 | ) | ||
1180 | } | ||
1181 | |||
1182 | #[test] | ||
1125 | fn doctest_replace_if_let_with_match() { | 1183 | fn doctest_replace_if_let_with_match() { |
1126 | check_doc_test( | 1184 | check_doc_test( |
1127 | "replace_if_let_with_match", | 1185 | "replace_if_let_with_match", |
diff --git a/crates/ide_assists/src/utils.rs b/crates/ide_assists/src/utils.rs index 276792bc1..880ab6fe3 100644 --- a/crates/ide_assists/src/utils.rs +++ b/crates/ide_assists/src/utils.rs | |||
@@ -21,7 +21,7 @@ use syntax::{ | |||
21 | }; | 21 | }; |
22 | 22 | ||
23 | use crate::{ | 23 | use crate::{ |
24 | assist_context::AssistContext, | 24 | assist_context::{AssistBuilder, AssistContext}, |
25 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, | 25 | ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams}, |
26 | }; | 26 | }; |
27 | 27 | ||
@@ -464,3 +464,25 @@ fn generate_impl_text_inner(adt: &ast::Adt, trait_text: Option<&str>, code: &str | |||
464 | 464 | ||
465 | buf | 465 | buf |
466 | } | 466 | } |
467 | |||
468 | pub(crate) fn add_method_to_adt( | ||
469 | builder: &mut AssistBuilder, | ||
470 | adt: &ast::Adt, | ||
471 | impl_def: Option<ast::Impl>, | ||
472 | method: &str, | ||
473 | ) { | ||
474 | let mut buf = String::with_capacity(method.len() + 2); | ||
475 | if impl_def.is_some() { | ||
476 | buf.push('\n'); | ||
477 | } | ||
478 | buf.push_str(method); | ||
479 | |||
480 | let start_offset = impl_def | ||
481 | .and_then(|impl_def| find_impl_block_end(impl_def, &mut buf)) | ||
482 | .unwrap_or_else(|| { | ||
483 | buf = generate_impl_text(&adt, &buf); | ||
484 | adt.syntax().text_range().end() | ||
485 | }); | ||
486 | |||
487 | builder.insert(start_offset, buf); | ||
488 | } | ||