aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers')
-rw-r--r--crates/ide_assists/src/handlers/inline_local_variable.rs223
-rw-r--r--crates/ide_assists/src/handlers/replace_derive_with_manual_impl.rs73
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 @@
1use ide_db::{defs::Definition, search::FileReference}; 1use either::Either;
2use hir::PathResolution;
3use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
2use rustc_hash::FxHashMap; 4use rustc_hash::FxHashMap;
3use syntax::{ 5use 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// ```
29pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 31pub(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
135struct InlineData {
136 let_stmt: ast::LetStmt,
137 delete_let: bool,
138 target: ast::NameOrNameRef,
139 replace_usages: FxHashMap<FileId, Vec<FileReference>>,
140}
141
142fn 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
173fn 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)]
144mod tests { 211mod 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#"
802fn f() {
803 let xyz = 0;
804 xyz$0;
805}
806"#,
807 r#"
808fn 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#"
820fn f() {
821 let xyz = 0;
822 xyz$0;
823 xyz;
824}
825"#,
826 r#"
827fn 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#"
841fn 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#"
854macro_rules! m {
855 ($i:ident) => { $i }
856}
857fn 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#"
866macro_rules! m {
867 ($i:ident) => { $i }
868}
869fn 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#"
211mod fmt { 216mod 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 {
221struct Foo { 226struct Foo {
222 bar: String, 227 bar: String,
223} 228}
224", 229"#,
225 " 230 r#"
226mod fmt { 231mod 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#"
252mod foo { 257mod foo {
253 pub trait Bar { 258 pub trait Bar {
254 type Qux; 259 type Qux;
@@ -263,8 +268,8 @@ mod foo {
263struct Foo { 268struct Foo {
264 bar: String, 269 bar: String,
265} 270}
266", 271"#,
267 " 272 r#"
268mod foo { 273mod 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)]
302struct Foo { 307struct Foo {
303 bar: String, 308 bar: String,
304} 309}
305 ", 310 "#,
306 " 311 r#"
307struct Foo { 312struct Foo {
308 bar: String, 313 bar: String,
309} 314}
@@ -311,7 +316,7 @@ struct Foo {
311impl Debug for Foo { 316impl 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)]
324pub struct Foo { 329pub struct Foo {
325 bar: String, 330 bar: String,
326} 331}
327 ", 332 "#,
328 " 333 r#"
329pub struct Foo { 334pub struct Foo {
330 bar: String, 335 bar: String,
331} 336}
@@ -333,7 +338,7 @@ pub struct Foo {
333impl Debug for Foo { 338impl 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)]
346struct Foo {} 351struct Foo {}
347 ", 352 "#,
348 " 353 r#"
349#[derive(Display, Serialize)] 354#[derive(Display, Serialize)]
350struct Foo {} 355struct Foo {}
351 356
352impl Debug for Foo { 357impl 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)]
365struct Foo {} 370struct 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)]
376struct Foo {} 381struct 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]
384struct Foo {} 389struct 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)]
395struct Foo {} 400struct 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)]
412struct S;
413 "#,
414 );
415 }
399} 416}