aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/inline_local_variable.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/inline_local_variable.rs')
-rw-r--r--crates/ide_assists/src/handlers/inline_local_variable.rs732
1 files changed, 732 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/inline_local_variable.rs b/crates/ide_assists/src/handlers/inline_local_variable.rs
new file mode 100644
index 000000000..da5522670
--- /dev/null
+++ b/crates/ide_assists/src/handlers/inline_local_variable.rs
@@ -0,0 +1,732 @@
1use ide_db::{defs::Definition, search::FileReference};
2use rustc_hash::FxHashMap;
3use syntax::{
4 ast::{self, AstNode, AstToken},
5 TextRange,
6};
7use test_utils::mark;
8
9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: inline_local_variable
15//
16// Inlines local variable.
17//
18// ```
19// fn main() {
20// let x$0 = 1 + 2;
21// x * 4;
22// }
23// ```
24// ->
25// ```
26// fn main() {
27// (1 + 2) * 4;
28// }
29// ```
30pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
32 let bind_pat = match let_stmt.pat()? {
33 ast::Pat::IdentPat(pat) => pat,
34 _ => return None,
35 };
36 if bind_pat.mut_token().is_some() {
37 mark::hit!(test_not_inline_mut_variable);
38 return None;
39 }
40 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
41 mark::hit!(not_applicable_outside_of_bind_pat);
42 return None;
43 }
44 let initializer_expr = let_stmt.initializer()?;
45
46 let def = ctx.sema.to_def(&bind_pat)?;
47 let def = Definition::Local(def);
48 let usages = def.usages(&ctx.sema).all();
49 if usages.is_empty() {
50 mark::hit!(test_not_applicable_if_variable_unused);
51 return None;
52 };
53
54 let delete_range = if let Some(whitespace) = let_stmt
55 .syntax()
56 .next_sibling_or_token()
57 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
58 {
59 TextRange::new(
60 let_stmt.syntax().text_range().start(),
61 whitespace.syntax().text_range().end(),
62 )
63 } else {
64 let_stmt.syntax().text_range()
65 };
66
67 let wrap_in_parens = usages
68 .references
69 .iter()
70 .map(|(&file_id, refs)| {
71 refs.iter()
72 .map(|&FileReference { range, .. }| {
73 let usage_node = ctx
74 .covering_node_for_range(range)
75 .ancestors()
76 .find_map(ast::PathExpr::cast)?;
77 let usage_parent_option =
78 usage_node.syntax().parent().and_then(ast::Expr::cast);
79 let usage_parent = match usage_parent_option {
80 Some(u) => u,
81 None => return Some(false),
82 };
83
84 Some(!matches!(
85 (&initializer_expr, usage_parent),
86 (ast::Expr::CallExpr(_), _)
87 | (ast::Expr::IndexExpr(_), _)
88 | (ast::Expr::MethodCallExpr(_), _)
89 | (ast::Expr::FieldExpr(_), _)
90 | (ast::Expr::TryExpr(_), _)
91 | (ast::Expr::RefExpr(_), _)
92 | (ast::Expr::Literal(_), _)
93 | (ast::Expr::TupleExpr(_), _)
94 | (ast::Expr::ArrayExpr(_), _)
95 | (ast::Expr::ParenExpr(_), _)
96 | (ast::Expr::PathExpr(_), _)
97 | (ast::Expr::BlockExpr(_), _)
98 | (ast::Expr::EffectExpr(_), _)
99 | (_, ast::Expr::CallExpr(_))
100 | (_, ast::Expr::TupleExpr(_))
101 | (_, ast::Expr::ArrayExpr(_))
102 | (_, ast::Expr::ParenExpr(_))
103 | (_, ast::Expr::ForExpr(_))
104 | (_, ast::Expr::WhileExpr(_))
105 | (_, ast::Expr::BreakExpr(_))
106 | (_, ast::Expr::ReturnExpr(_))
107 | (_, ast::Expr::MatchExpr(_))
108 ))
109 })
110 .collect::<Option<_>>()
111 .map(|b| (file_id, b))
112 })
113 .collect::<Option<FxHashMap<_, Vec<_>>>>()?;
114
115 let init_str = initializer_expr.syntax().text().to_string();
116 let init_in_paren = format!("({})", &init_str);
117
118 let target = bind_pat.syntax().text_range();
119 acc.add(
120 AssistId("inline_local_variable", AssistKind::RefactorInline),
121 "Inline variable",
122 target,
123 move |builder| {
124 builder.delete(delete_range);
125 for (file_id, references) in usages.references {
126 for (&should_wrap, reference) in wrap_in_parens[&file_id].iter().zip(references) {
127 let replacement =
128 if should_wrap { init_in_paren.clone() } else { init_str.clone() };
129 match reference.name.as_name_ref() {
130 Some(name_ref)
131 if ast::RecordExprField::for_field_name(name_ref).is_some() =>
132 {
133 mark::hit!(inline_field_shorthand);
134 builder.insert(reference.range.end(), format!(": {}", replacement));
135 }
136 _ => builder.replace(reference.range, replacement),
137 }
138 }
139 }
140 },
141 )
142}
143
144#[cfg(test)]
145mod tests {
146 use test_utils::mark;
147
148 use crate::tests::{check_assist, check_assist_not_applicable};
149
150 use super::*;
151
152 #[test]
153 fn test_inline_let_bind_literal_expr() {
154 check_assist(
155 inline_local_variable,
156 r"
157fn bar(a: usize) {}
158fn foo() {
159 let a$0 = 1;
160 a + 1;
161 if a > 10 {
162 }
163
164 while a > 10 {
165
166 }
167 let b = a * 10;
168 bar(a);
169}",
170 r"
171fn bar(a: usize) {}
172fn foo() {
173 1 + 1;
174 if 1 > 10 {
175 }
176
177 while 1 > 10 {
178
179 }
180 let b = 1 * 10;
181 bar(1);
182}",
183 );
184 }
185
186 #[test]
187 fn test_inline_let_bind_bin_expr() {
188 check_assist(
189 inline_local_variable,
190 r"
191fn bar(a: usize) {}
192fn foo() {
193 let a$0 = 1 + 1;
194 a + 1;
195 if a > 10 {
196 }
197
198 while a > 10 {
199
200 }
201 let b = a * 10;
202 bar(a);
203}",
204 r"
205fn bar(a: usize) {}
206fn foo() {
207 (1 + 1) + 1;
208 if (1 + 1) > 10 {
209 }
210
211 while (1 + 1) > 10 {
212
213 }
214 let b = (1 + 1) * 10;
215 bar(1 + 1);
216}",
217 );
218 }
219
220 #[test]
221 fn test_inline_let_bind_function_call_expr() {
222 check_assist(
223 inline_local_variable,
224 r"
225fn bar(a: usize) {}
226fn foo() {
227 let a$0 = bar(1);
228 a + 1;
229 if a > 10 {
230 }
231
232 while a > 10 {
233
234 }
235 let b = a * 10;
236 bar(a);
237}",
238 r"
239fn bar(a: usize) {}
240fn foo() {
241 bar(1) + 1;
242 if bar(1) > 10 {
243 }
244
245 while bar(1) > 10 {
246
247 }
248 let b = bar(1) * 10;
249 bar(bar(1));
250}",
251 );
252 }
253
254 #[test]
255 fn test_inline_let_bind_cast_expr() {
256 check_assist(
257 inline_local_variable,
258 r"
259fn bar(a: usize): usize { a }
260fn foo() {
261 let a$0 = bar(1) as u64;
262 a + 1;
263 if a > 10 {
264 }
265
266 while a > 10 {
267
268 }
269 let b = a * 10;
270 bar(a);
271}",
272 r"
273fn bar(a: usize): usize { a }
274fn foo() {
275 (bar(1) as u64) + 1;
276 if (bar(1) as u64) > 10 {
277 }
278
279 while (bar(1) as u64) > 10 {
280
281 }
282 let b = (bar(1) as u64) * 10;
283 bar(bar(1) as u64);
284}",
285 );
286 }
287
288 #[test]
289 fn test_inline_let_bind_block_expr() {
290 check_assist(
291 inline_local_variable,
292 r"
293fn foo() {
294 let a$0 = { 10 + 1 };
295 a + 1;
296 if a > 10 {
297 }
298
299 while a > 10 {
300
301 }
302 let b = a * 10;
303 bar(a);
304}",
305 r"
306fn foo() {
307 { 10 + 1 } + 1;
308 if { 10 + 1 } > 10 {
309 }
310
311 while { 10 + 1 } > 10 {
312
313 }
314 let b = { 10 + 1 } * 10;
315 bar({ 10 + 1 });
316}",
317 );
318 }
319
320 #[test]
321 fn test_inline_let_bind_paren_expr() {
322 check_assist(
323 inline_local_variable,
324 r"
325fn foo() {
326 let a$0 = ( 10 + 1 );
327 a + 1;
328 if a > 10 {
329 }
330
331 while a > 10 {
332
333 }
334 let b = a * 10;
335 bar(a);
336}",
337 r"
338fn foo() {
339 ( 10 + 1 ) + 1;
340 if ( 10 + 1 ) > 10 {
341 }
342
343 while ( 10 + 1 ) > 10 {
344
345 }
346 let b = ( 10 + 1 ) * 10;
347 bar(( 10 + 1 ));
348}",
349 );
350 }
351
352 #[test]
353 fn test_not_inline_mut_variable() {
354 mark::check!(test_not_inline_mut_variable);
355 check_assist_not_applicable(
356 inline_local_variable,
357 r"
358fn foo() {
359 let mut a$0 = 1 + 1;
360 a + 1;
361}",
362 );
363 }
364
365 #[test]
366 fn test_call_expr() {
367 check_assist(
368 inline_local_variable,
369 r"
370fn foo() {
371 let a$0 = bar(10 + 1);
372 let b = a * 10;
373 let c = a as usize;
374}",
375 r"
376fn foo() {
377 let b = bar(10 + 1) * 10;
378 let c = bar(10 + 1) as usize;
379}",
380 );
381 }
382
383 #[test]
384 fn test_index_expr() {
385 check_assist(
386 inline_local_variable,
387 r"
388fn foo() {
389 let x = vec![1, 2, 3];
390 let a$0 = x[0];
391 let b = a * 10;
392 let c = a as usize;
393}",
394 r"
395fn foo() {
396 let x = vec![1, 2, 3];
397 let b = x[0] * 10;
398 let c = x[0] as usize;
399}",
400 );
401 }
402
403 #[test]
404 fn test_method_call_expr() {
405 check_assist(
406 inline_local_variable,
407 r"
408fn foo() {
409 let bar = vec![1];
410 let a$0 = bar.len();
411 let b = a * 10;
412 let c = a as usize;
413}",
414 r"
415fn foo() {
416 let bar = vec![1];
417 let b = bar.len() * 10;
418 let c = bar.len() as usize;
419}",
420 );
421 }
422
423 #[test]
424 fn test_field_expr() {
425 check_assist(
426 inline_local_variable,
427 r"
428struct Bar {
429 foo: usize
430}
431
432fn foo() {
433 let bar = Bar { foo: 1 };
434 let a$0 = bar.foo;
435 let b = a * 10;
436 let c = a as usize;
437}",
438 r"
439struct Bar {
440 foo: usize
441}
442
443fn foo() {
444 let bar = Bar { foo: 1 };
445 let b = bar.foo * 10;
446 let c = bar.foo as usize;
447}",
448 );
449 }
450
451 #[test]
452 fn test_try_expr() {
453 check_assist(
454 inline_local_variable,
455 r"
456fn foo() -> Option<usize> {
457 let bar = Some(1);
458 let a$0 = bar?;
459 let b = a * 10;
460 let c = a as usize;
461 None
462}",
463 r"
464fn foo() -> Option<usize> {
465 let bar = Some(1);
466 let b = bar? * 10;
467 let c = bar? as usize;
468 None
469}",
470 );
471 }
472
473 #[test]
474 fn test_ref_expr() {
475 check_assist(
476 inline_local_variable,
477 r"
478fn foo() {
479 let bar = 10;
480 let a$0 = &bar;
481 let b = a * 10;
482}",
483 r"
484fn foo() {
485 let bar = 10;
486 let b = &bar * 10;
487}",
488 );
489 }
490
491 #[test]
492 fn test_tuple_expr() {
493 check_assist(
494 inline_local_variable,
495 r"
496fn foo() {
497 let a$0 = (10, 20);
498 let b = a[0];
499}",
500 r"
501fn foo() {
502 let b = (10, 20)[0];
503}",
504 );
505 }
506
507 #[test]
508 fn test_array_expr() {
509 check_assist(
510 inline_local_variable,
511 r"
512fn foo() {
513 let a$0 = [1, 2, 3];
514 let b = a.len();
515}",
516 r"
517fn foo() {
518 let b = [1, 2, 3].len();
519}",
520 );
521 }
522
523 #[test]
524 fn test_paren() {
525 check_assist(
526 inline_local_variable,
527 r"
528fn foo() {
529 let a$0 = (10 + 20);
530 let b = a * 10;
531 let c = a as usize;
532}",
533 r"
534fn foo() {
535 let b = (10 + 20) * 10;
536 let c = (10 + 20) as usize;
537}",
538 );
539 }
540
541 #[test]
542 fn test_path_expr() {
543 check_assist(
544 inline_local_variable,
545 r"
546fn foo() {
547 let d = 10;
548 let a$0 = d;
549 let b = a * 10;
550 let c = a as usize;
551}",
552 r"
553fn foo() {
554 let d = 10;
555 let b = d * 10;
556 let c = d as usize;
557}",
558 );
559 }
560
561 #[test]
562 fn test_block_expr() {
563 check_assist(
564 inline_local_variable,
565 r"
566fn foo() {
567 let a$0 = { 10 };
568 let b = a * 10;
569 let c = a as usize;
570}",
571 r"
572fn foo() {
573 let b = { 10 } * 10;
574 let c = { 10 } as usize;
575}",
576 );
577 }
578
579 #[test]
580 fn test_used_in_different_expr1() {
581 check_assist(
582 inline_local_variable,
583 r"
584fn foo() {
585 let a$0 = 10 + 20;
586 let b = a * 10;
587 let c = (a, 20);
588 let d = [a, 10];
589 let e = (a);
590}",
591 r"
592fn foo() {
593 let b = (10 + 20) * 10;
594 let c = (10 + 20, 20);
595 let d = [10 + 20, 10];
596 let e = (10 + 20);
597}",
598 );
599 }
600
601 #[test]
602 fn test_used_in_for_expr() {
603 check_assist(
604 inline_local_variable,
605 r"
606fn foo() {
607 let a$0 = vec![10, 20];
608 for i in a {}
609}",
610 r"
611fn foo() {
612 for i in vec![10, 20] {}
613}",
614 );
615 }
616
617 #[test]
618 fn test_used_in_while_expr() {
619 check_assist(
620 inline_local_variable,
621 r"
622fn foo() {
623 let a$0 = 1 > 0;
624 while a {}
625}",
626 r"
627fn foo() {
628 while 1 > 0 {}
629}",
630 );
631 }
632
633 #[test]
634 fn test_used_in_break_expr() {
635 check_assist(
636 inline_local_variable,
637 r"
638fn foo() {
639 let a$0 = 1 + 1;
640 loop {
641 break a;
642 }
643}",
644 r"
645fn foo() {
646 loop {
647 break 1 + 1;
648 }
649}",
650 );
651 }
652
653 #[test]
654 fn test_used_in_return_expr() {
655 check_assist(
656 inline_local_variable,
657 r"
658fn foo() {
659 let a$0 = 1 > 0;
660 return a;
661}",
662 r"
663fn foo() {
664 return 1 > 0;
665}",
666 );
667 }
668
669 #[test]
670 fn test_used_in_match_expr() {
671 check_assist(
672 inline_local_variable,
673 r"
674fn foo() {
675 let a$0 = 1 > 0;
676 match a {}
677}",
678 r"
679fn foo() {
680 match 1 > 0 {}
681}",
682 );
683 }
684
685 #[test]
686 fn inline_field_shorthand() {
687 mark::check!(inline_field_shorthand);
688 check_assist(
689 inline_local_variable,
690 r"
691struct S { foo: i32}
692fn main() {
693 let $0foo = 92;
694 S { foo }
695}
696",
697 r"
698struct S { foo: i32}
699fn main() {
700 S { foo: 92 }
701}
702",
703 );
704 }
705
706 #[test]
707 fn test_not_applicable_if_variable_unused() {
708 mark::check!(test_not_applicable_if_variable_unused);
709 check_assist_not_applicable(
710 inline_local_variable,
711 r"
712fn foo() {
713 let $0a = 0;
714}
715 ",
716 )
717 }
718
719 #[test]
720 fn not_applicable_outside_of_bind_pat() {
721 mark::check!(not_applicable_outside_of_bind_pat);
722 check_assist_not_applicable(
723 inline_local_variable,
724 r"
725fn main() {
726 let x = $01 + 2;
727 x * 4;
728}
729",
730 )
731 }
732}