diff options
Diffstat (limited to 'crates/ide_assists/src/handlers')
-rw-r--r-- | crates/ide_assists/src/handlers/inline_local_variable.rs | 223 | ||||
-rw-r--r-- | crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | 73 |
2 files changed, 230 insertions, 66 deletions
diff --git a/crates/ide_assists/src/handlers/inline_local_variable.rs b/crates/ide_assists/src/handlers/inline_local_variable.rs index ea1466dc8..f5dafc8cb 100644 --- a/crates/ide_assists/src/handlers/inline_local_variable.rs +++ b/crates/ide_assists/src/handlers/inline_local_variable.rs | |||
@@ -1,7 +1,9 @@ | |||
1 | use ide_db::{defs::Definition, search::FileReference}; | 1 | use either::Either; |
2 | use hir::PathResolution; | ||
3 | use ide_db::{base_db::FileId, defs::Definition, search::FileReference}; | ||
2 | use rustc_hash::FxHashMap; | 4 | use rustc_hash::FxHashMap; |
3 | use syntax::{ | 5 | use syntax::{ |
4 | ast::{self, AstNode, AstToken}, | 6 | ast::{self, AstNode, AstToken, NameOwner}, |
5 | TextRange, | 7 | TextRange, |
6 | }; | 8 | }; |
7 | 9 | ||
@@ -27,44 +29,28 @@ use crate::{ | |||
27 | // } | 29 | // } |
28 | // ``` | 30 | // ``` |
29 | pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 31 | pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
30 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | 32 | let InlineData { let_stmt, delete_let, replace_usages, target } = |
31 | let bind_pat = match let_stmt.pat()? { | 33 | inline_let(ctx).or_else(|| inline_usage(ctx))?; |
32 | ast::Pat::IdentPat(pat) => pat, | ||
33 | _ => return None, | ||
34 | }; | ||
35 | if bind_pat.mut_token().is_some() { | ||
36 | cov_mark::hit!(test_not_inline_mut_variable); | ||
37 | return None; | ||
38 | } | ||
39 | if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { | ||
40 | cov_mark::hit!(not_applicable_outside_of_bind_pat); | ||
41 | return None; | ||
42 | } | ||
43 | let initializer_expr = let_stmt.initializer()?; | 34 | let initializer_expr = let_stmt.initializer()?; |
44 | 35 | ||
45 | let def = ctx.sema.to_def(&bind_pat)?; | 36 | let delete_range = if delete_let { |
46 | let def = Definition::Local(def); | 37 | if let Some(whitespace) = let_stmt |
47 | let usages = def.usages(&ctx.sema).all(); | 38 | .syntax() |
48 | if usages.is_empty() { | 39 | .next_sibling_or_token() |
49 | cov_mark::hit!(test_not_applicable_if_variable_unused); | 40 | .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) |
50 | return None; | 41 | { |
51 | }; | 42 | Some(TextRange::new( |
52 | 43 | let_stmt.syntax().text_range().start(), | |
53 | let delete_range = if let Some(whitespace) = let_stmt | 44 | whitespace.syntax().text_range().end(), |
54 | .syntax() | 45 | )) |
55 | .next_sibling_or_token() | 46 | } else { |
56 | .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone())) | 47 | Some(let_stmt.syntax().text_range()) |
57 | { | 48 | } |
58 | TextRange::new( | ||
59 | let_stmt.syntax().text_range().start(), | ||
60 | whitespace.syntax().text_range().end(), | ||
61 | ) | ||
62 | } else { | 49 | } else { |
63 | let_stmt.syntax().text_range() | 50 | None |
64 | }; | 51 | }; |
65 | 52 | ||
66 | let wrap_in_parens = usages | 53 | let wrap_in_parens = replace_usages |
67 | .references | ||
68 | .iter() | 54 | .iter() |
69 | .map(|(&file_id, refs)| { | 55 | .map(|(&file_id, refs)| { |
70 | refs.iter() | 56 | refs.iter() |
@@ -114,14 +100,20 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
114 | let init_str = initializer_expr.syntax().text().to_string(); | 100 | let init_str = initializer_expr.syntax().text().to_string(); |
115 | let init_in_paren = format!("({})", &init_str); | 101 | let init_in_paren = format!("({})", &init_str); |
116 | 102 | ||
117 | let target = bind_pat.syntax().text_range(); | 103 | let target = match target { |
104 | ast::NameOrNameRef::Name(it) => it.syntax().text_range(), | ||
105 | ast::NameOrNameRef::NameRef(it) => it.syntax().text_range(), | ||
106 | }; | ||
107 | |||
118 | acc.add( | 108 | acc.add( |
119 | AssistId("inline_local_variable", AssistKind::RefactorInline), | 109 | AssistId("inline_local_variable", AssistKind::RefactorInline), |
120 | "Inline variable", | 110 | "Inline variable", |
121 | target, | 111 | target, |
122 | move |builder| { | 112 | move |builder| { |
123 | builder.delete(delete_range); | 113 | if let Some(range) = delete_range { |
124 | for (file_id, references) in usages.references { | 114 | builder.delete(range); |
115 | } | ||
116 | for (file_id, references) in replace_usages { | ||
125 | for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) { | 117 | for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) { |
126 | let replacement = | 118 | let replacement = |
127 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; | 119 | if should_wrap { init_in_paren.clone() } else { init_str.clone() }; |
@@ -140,6 +132,81 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> O | |||
140 | ) | 132 | ) |
141 | } | 133 | } |
142 | 134 | ||
135 | struct InlineData { | ||
136 | let_stmt: ast::LetStmt, | ||
137 | delete_let: bool, | ||
138 | target: ast::NameOrNameRef, | ||
139 | replace_usages: FxHashMap<FileId, Vec<FileReference>>, | ||
140 | } | ||
141 | |||
142 | fn inline_let(ctx: &AssistContext) -> Option<InlineData> { | ||
143 | let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?; | ||
144 | let bind_pat = match let_stmt.pat()? { | ||
145 | ast::Pat::IdentPat(pat) => pat, | ||
146 | _ => return None, | ||
147 | }; | ||
148 | if bind_pat.mut_token().is_some() { | ||
149 | cov_mark::hit!(test_not_inline_mut_variable); | ||
150 | return None; | ||
151 | } | ||
152 | if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) { | ||
153 | cov_mark::hit!(not_applicable_outside_of_bind_pat); | ||
154 | return None; | ||
155 | } | ||
156 | |||
157 | let def = ctx.sema.to_def(&bind_pat)?; | ||
158 | let def = Definition::Local(def); | ||
159 | let usages = def.usages(&ctx.sema).all(); | ||
160 | if usages.is_empty() { | ||
161 | cov_mark::hit!(test_not_applicable_if_variable_unused); | ||
162 | return None; | ||
163 | }; | ||
164 | |||
165 | Some(InlineData { | ||
166 | let_stmt, | ||
167 | delete_let: true, | ||
168 | target: ast::NameOrNameRef::Name(bind_pat.name()?), | ||
169 | replace_usages: usages.references, | ||
170 | }) | ||
171 | } | ||
172 | |||
173 | fn inline_usage(ctx: &AssistContext) -> Option<InlineData> { | ||
174 | let path_expr = ctx.find_node_at_offset::<ast::PathExpr>()?; | ||
175 | let path = path_expr.path()?; | ||
176 | let name = match path.as_single_segment()?.kind()? { | ||
177 | ast::PathSegmentKind::Name(name) => name, | ||
178 | _ => return None, | ||
179 | }; | ||
180 | |||
181 | let local = match ctx.sema.resolve_path(&path)? { | ||
182 | PathResolution::Local(local) => local, | ||
183 | _ => return None, | ||
184 | }; | ||
185 | |||
186 | let bind_pat = match local.source(ctx.db()).value { | ||
187 | Either::Left(ident) => ident, | ||
188 | _ => return None, | ||
189 | }; | ||
190 | |||
191 | let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?; | ||
192 | |||
193 | let def = Definition::Local(local); | ||
194 | let mut usages = def.usages(&ctx.sema).all(); | ||
195 | |||
196 | let delete_let = usages.references.values().map(|v| v.len()).sum::<usize>() == 1; | ||
197 | |||
198 | for references in usages.references.values_mut() { | ||
199 | references.retain(|reference| reference.name.as_name_ref() == Some(&name)); | ||
200 | } | ||
201 | |||
202 | Some(InlineData { | ||
203 | let_stmt, | ||
204 | delete_let, | ||
205 | target: ast::NameOrNameRef::NameRef(name), | ||
206 | replace_usages: usages.references, | ||
207 | }) | ||
208 | } | ||
209 | |||
143 | #[cfg(test)] | 210 | #[cfg(test)] |
144 | mod tests { | 211 | mod tests { |
145 | use crate::tests::{check_assist, check_assist_not_applicable}; | 212 | use crate::tests::{check_assist, check_assist_not_applicable}; |
@@ -726,4 +793,84 @@ fn main() { | |||
726 | ", | 793 | ", |
727 | ) | 794 | ) |
728 | } | 795 | } |
796 | |||
797 | #[test] | ||
798 | fn works_on_local_usage() { | ||
799 | check_assist( | ||
800 | inline_local_variable, | ||
801 | r#" | ||
802 | fn f() { | ||
803 | let xyz = 0; | ||
804 | xyz$0; | ||
805 | } | ||
806 | "#, | ||
807 | r#" | ||
808 | fn f() { | ||
809 | 0; | ||
810 | } | ||
811 | "#, | ||
812 | ); | ||
813 | } | ||
814 | |||
815 | #[test] | ||
816 | fn does_not_remove_let_when_multiple_usages() { | ||
817 | check_assist( | ||
818 | inline_local_variable, | ||
819 | r#" | ||
820 | fn f() { | ||
821 | let xyz = 0; | ||
822 | xyz$0; | ||
823 | xyz; | ||
824 | } | ||
825 | "#, | ||
826 | r#" | ||
827 | fn f() { | ||
828 | let xyz = 0; | ||
829 | 0; | ||
830 | xyz; | ||
831 | } | ||
832 | "#, | ||
833 | ); | ||
834 | } | ||
835 | |||
836 | #[test] | ||
837 | fn not_applicable_with_non_ident_pattern() { | ||
838 | check_assist_not_applicable( | ||
839 | inline_local_variable, | ||
840 | r#" | ||
841 | fn main() { | ||
842 | let (x, y) = (0, 1); | ||
843 | x$0; | ||
844 | } | ||
845 | "#, | ||
846 | ); | ||
847 | } | ||
848 | |||
849 | #[test] | ||
850 | fn not_applicable_on_local_usage_in_macro() { | ||
851 | check_assist_not_applicable( | ||
852 | inline_local_variable, | ||
853 | r#" | ||
854 | macro_rules! m { | ||
855 | ($i:ident) => { $i } | ||
856 | } | ||
857 | fn f() { | ||
858 | let xyz = 0; | ||
859 | m!(xyz$0); // replacing it would break the macro | ||
860 | } | ||
861 | "#, | ||
862 | ); | ||
863 | check_assist_not_applicable( | ||
864 | inline_local_variable, | ||
865 | r#" | ||
866 | macro_rules! m { | ||
867 | ($i:ident) => { $i } | ||
868 | } | ||
869 | fn f() { | ||
870 | let xyz$0 = 0; | ||
871 | m!(xyz); // replacing it would break the macro | ||
872 | } | ||
873 | "#, | ||
874 | ); | ||
875 | } | ||
729 | } | 876 | } |
diff --git a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs index 870a8d4ff..694d897d1 100644 --- a/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs | |||
@@ -47,6 +47,11 @@ pub(crate) fn replace_derive_with_manual_impl( | |||
47 | return None; | 47 | return None; |
48 | } | 48 | } |
49 | 49 | ||
50 | if !args.syntax().text_range().contains(ctx.offset()) { | ||
51 | cov_mark::hit!(outside_of_attr_args); | ||
52 | return None; | ||
53 | } | ||
54 | |||
50 | let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?; | 55 | let trait_token = args.syntax().token_at_offset(ctx.offset()).find(|t| t.kind() == IDENT)?; |
51 | let trait_name = trait_token.text(); | 56 | let trait_name = trait_token.text(); |
52 | 57 | ||
@@ -207,7 +212,7 @@ mod tests { | |||
207 | fn add_custom_impl_debug() { | 212 | fn add_custom_impl_debug() { |
208 | check_assist( | 213 | check_assist( |
209 | replace_derive_with_manual_impl, | 214 | replace_derive_with_manual_impl, |
210 | " | 215 | r#" |
211 | mod fmt { | 216 | mod fmt { |
212 | pub struct Error; | 217 | pub struct Error; |
213 | pub type Result = Result<(), Error>; | 218 | pub type Result = Result<(), Error>; |
@@ -221,8 +226,8 @@ mod fmt { | |||
221 | struct Foo { | 226 | struct Foo { |
222 | bar: String, | 227 | bar: String, |
223 | } | 228 | } |
224 | ", | 229 | "#, |
225 | " | 230 | r#" |
226 | mod fmt { | 231 | mod fmt { |
227 | pub struct Error; | 232 | pub struct Error; |
228 | pub type Result = Result<(), Error>; | 233 | pub type Result = Result<(), Error>; |
@@ -241,14 +246,14 @@ impl fmt::Debug for Foo { | |||
241 | ${0:todo!()} | 246 | ${0:todo!()} |
242 | } | 247 | } |
243 | } | 248 | } |
244 | ", | 249 | "#, |
245 | ) | 250 | ) |
246 | } | 251 | } |
247 | #[test] | 252 | #[test] |
248 | fn add_custom_impl_all() { | 253 | fn add_custom_impl_all() { |
249 | check_assist( | 254 | check_assist( |
250 | replace_derive_with_manual_impl, | 255 | replace_derive_with_manual_impl, |
251 | " | 256 | r#" |
252 | mod foo { | 257 | mod foo { |
253 | pub trait Bar { | 258 | pub trait Bar { |
254 | type Qux; | 259 | type Qux; |
@@ -263,8 +268,8 @@ mod foo { | |||
263 | struct Foo { | 268 | struct Foo { |
264 | bar: String, | 269 | bar: String, |
265 | } | 270 | } |
266 | ", | 271 | "#, |
267 | " | 272 | r#" |
268 | mod foo { | 273 | mod foo { |
269 | pub trait Bar { | 274 | pub trait Bar { |
270 | type Qux; | 275 | type Qux; |
@@ -290,20 +295,20 @@ impl foo::Bar for Foo { | |||
290 | todo!() | 295 | todo!() |
291 | } | 296 | } |
292 | } | 297 | } |
293 | ", | 298 | "#, |
294 | ) | 299 | ) |
295 | } | 300 | } |
296 | #[test] | 301 | #[test] |
297 | fn add_custom_impl_for_unique_input() { | 302 | fn add_custom_impl_for_unique_input() { |
298 | check_assist( | 303 | check_assist( |
299 | replace_derive_with_manual_impl, | 304 | replace_derive_with_manual_impl, |
300 | " | 305 | r#" |
301 | #[derive(Debu$0g)] | 306 | #[derive(Debu$0g)] |
302 | struct Foo { | 307 | struct Foo { |
303 | bar: String, | 308 | bar: String, |
304 | } | 309 | } |
305 | ", | 310 | "#, |
306 | " | 311 | r#" |
307 | struct Foo { | 312 | struct Foo { |
308 | bar: String, | 313 | bar: String, |
309 | } | 314 | } |
@@ -311,7 +316,7 @@ struct Foo { | |||
311 | impl Debug for Foo { | 316 | impl Debug for Foo { |
312 | $0 | 317 | $0 |
313 | } | 318 | } |
314 | ", | 319 | "#, |
315 | ) | 320 | ) |
316 | } | 321 | } |
317 | 322 | ||
@@ -319,13 +324,13 @@ impl Debug for Foo { | |||
319 | fn add_custom_impl_for_with_visibility_modifier() { | 324 | fn add_custom_impl_for_with_visibility_modifier() { |
320 | check_assist( | 325 | check_assist( |
321 | replace_derive_with_manual_impl, | 326 | replace_derive_with_manual_impl, |
322 | " | 327 | r#" |
323 | #[derive(Debug$0)] | 328 | #[derive(Debug$0)] |
324 | pub struct Foo { | 329 | pub struct Foo { |
325 | bar: String, | 330 | bar: String, |
326 | } | 331 | } |
327 | ", | 332 | "#, |
328 | " | 333 | r#" |
329 | pub struct Foo { | 334 | pub struct Foo { |
330 | bar: String, | 335 | bar: String, |
331 | } | 336 | } |
@@ -333,7 +338,7 @@ pub struct Foo { | |||
333 | impl Debug for Foo { | 338 | impl Debug for Foo { |
334 | $0 | 339 | $0 |
335 | } | 340 | } |
336 | ", | 341 | "#, |
337 | ) | 342 | ) |
338 | } | 343 | } |
339 | 344 | ||
@@ -341,18 +346,18 @@ impl Debug for Foo { | |||
341 | fn add_custom_impl_when_multiple_inputs() { | 346 | fn add_custom_impl_when_multiple_inputs() { |
342 | check_assist( | 347 | check_assist( |
343 | replace_derive_with_manual_impl, | 348 | replace_derive_with_manual_impl, |
344 | " | 349 | r#" |
345 | #[derive(Display, Debug$0, Serialize)] | 350 | #[derive(Display, Debug$0, Serialize)] |
346 | struct Foo {} | 351 | struct Foo {} |
347 | ", | 352 | "#, |
348 | " | 353 | r#" |
349 | #[derive(Display, Serialize)] | 354 | #[derive(Display, Serialize)] |
350 | struct Foo {} | 355 | struct Foo {} |
351 | 356 | ||
352 | impl Debug for Foo { | 357 | impl Debug for Foo { |
353 | $0 | 358 | $0 |
354 | } | 359 | } |
355 | ", | 360 | "#, |
356 | ) | 361 | ) |
357 | } | 362 | } |
358 | 363 | ||
@@ -360,10 +365,10 @@ impl Debug for Foo { | |||
360 | fn test_ignore_derive_macro_without_input() { | 365 | fn test_ignore_derive_macro_without_input() { |
361 | check_assist_not_applicable( | 366 | check_assist_not_applicable( |
362 | replace_derive_with_manual_impl, | 367 | replace_derive_with_manual_impl, |
363 | " | 368 | r#" |
364 | #[derive($0)] | 369 | #[derive($0)] |
365 | struct Foo {} | 370 | struct Foo {} |
366 | ", | 371 | "#, |
367 | ) | 372 | ) |
368 | } | 373 | } |
369 | 374 | ||
@@ -371,18 +376,18 @@ struct Foo {} | |||
371 | fn test_ignore_if_cursor_on_param() { | 376 | fn test_ignore_if_cursor_on_param() { |
372 | check_assist_not_applicable( | 377 | check_assist_not_applicable( |
373 | replace_derive_with_manual_impl, | 378 | replace_derive_with_manual_impl, |
374 | " | 379 | r#" |
375 | #[derive$0(Debug)] | 380 | #[derive$0(Debug)] |
376 | struct Foo {} | 381 | struct Foo {} |
377 | ", | 382 | "#, |
378 | ); | 383 | ); |
379 | 384 | ||
380 | check_assist_not_applicable( | 385 | check_assist_not_applicable( |
381 | replace_derive_with_manual_impl, | 386 | replace_derive_with_manual_impl, |
382 | " | 387 | r#" |
383 | #[derive(Debug)$0] | 388 | #[derive(Debug)$0] |
384 | struct Foo {} | 389 | struct Foo {} |
385 | ", | 390 | "#, |
386 | ) | 391 | ) |
387 | } | 392 | } |
388 | 393 | ||
@@ -390,10 +395,22 @@ struct Foo {} | |||
390 | fn test_ignore_if_not_derive() { | 395 | fn test_ignore_if_not_derive() { |
391 | check_assist_not_applicable( | 396 | check_assist_not_applicable( |
392 | replace_derive_with_manual_impl, | 397 | replace_derive_with_manual_impl, |
393 | " | 398 | r#" |
394 | #[allow(non_camel_$0case_types)] | 399 | #[allow(non_camel_$0case_types)] |
395 | struct Foo {} | 400 | struct Foo {} |
396 | ", | 401 | "#, |
397 | ) | 402 | ) |
398 | } | 403 | } |
404 | |||
405 | #[test] | ||
406 | fn works_at_start_of_file() { | ||
407 | cov_mark::check!(outside_of_attr_args); | ||
408 | check_assist_not_applicable( | ||
409 | replace_derive_with_manual_impl, | ||
410 | r#" | ||
411 | $0#[derive(Debug)] | ||
412 | struct S; | ||
413 | "#, | ||
414 | ); | ||
415 | } | ||
399 | } | 416 | } |