1use hir::{HirDisplay, TypeInfo};
2use ide_db::{
3 assists::GroupLabel,
4 syntax_helpers::{LexedStr, suggest_name},
5};
6use syntax::{
7 Direction, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken, T, TextRange,
8 algo::{ancestors_at_offset, skip_trivia_token},
9 ast::{
10 self, AstNode,
11 edit::{AstNodeEdit, IndentLevel},
12 },
13 syntax_editor::{Element, Position},
14};
15
16use crate::{AssistContext, AssistId, Assists, utils::is_body_const};
17
18pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
69 let node = if ctx.has_empty_selection() {
70 if let Some(t) = ctx.token_at_offset().find(|it| it.kind() == T![;]) {
71 t.parent().and_then(ast::ExprStmt::cast)?.syntax().clone()
72 } else if let Some(expr) = ancestors_at_offset(ctx.source_file().syntax(), ctx.offset())
73 .next()
74 .and_then(ast::Expr::cast)
75 {
76 expr.syntax().ancestors().find_map(valid_target_expr(ctx))?.syntax().clone()
77 } else {
78 return None;
79 }
80 } else {
81 match ctx.covering_element() {
82 NodeOrToken::Node(it) => it,
83 NodeOrToken::Token(it) if it.kind() == SyntaxKind::COMMENT => {
84 cov_mark::hit!(extract_var_in_comment_is_not_applicable);
85 return None;
86 }
87 NodeOrToken::Token(it) => it.parent()?,
88 }
89 };
90
91 let node = node.ancestors().take_while(|anc| anc.text_range() == node.text_range()).last()?;
92 let range = node.text_range();
93
94 let (to_replace, analysis) = if node.kind() == SyntaxKind::TOKEN_TREE {
95 let (first, last) = extract_token_range_of(&node, ctx.selection_trimmed())?;
96
97 let first_descend = ctx.sema.descend_into_macros_single_exact(first.clone());
98 let last_descend = ctx.sema.descend_into_macros_single_exact(last.clone());
99 let range = first_descend.text_range().cover(last_descend.text_range());
100
101 if first_descend.parent_ancestors().last() != last_descend.parent_ancestors().last() {
102 return None;
103 }
104
105 let expr = first_descend
106 .parent_ancestors()
107 .skip_while(|it| !it.text_range().contains_range(range))
108 .find_map(valid_target_expr(ctx))?;
109 let original_range = ctx.sema.original_range(expr.syntax());
110 let (first, last) = extract_token_range_of(&node, original_range.range)?;
111 let to_extract = first.syntax_element()..=last.syntax_element();
112 (to_extract, expr)
113 } else {
114 let expr = node
115 .descendants()
116 .take_while(|it| range.contains_range(it.text_range()))
117 .find_map(valid_target_expr(ctx))?;
118 let to_extract = expr.syntax().syntax_element();
119 (to_extract.clone()..=to_extract, expr)
120 };
121 let place = match to_replace.start() {
122 NodeOrToken::Node(node) => node.clone(),
123 NodeOrToken::Token(t) => t.parent()?,
124 };
125
126 let ty = ctx.sema.type_of_expr(&analysis).map(TypeInfo::adjusted);
127 if matches!(&ty, Some(ty_info) if ty_info.is_unit()) {
128 return None;
129 }
130
131 let parent = analysis.syntax().parent().and_then(ast::Expr::cast);
132 let mut needs_adjust = parent.as_ref().is_some_and(|it| match it {
134 ast::Expr::FieldExpr(_)
135 | ast::Expr::MethodCallExpr(_)
136 | ast::Expr::CallExpr(_)
137 | ast::Expr::AwaitExpr(_) => true,
138 ast::Expr::IndexExpr(index) if index.base().as_ref() == Some(&analysis) => true,
139 _ => false,
140 });
141 let mut to_extract_no_ref = peel_parens(analysis.clone());
142 let needs_ref = needs_adjust
143 && match &to_extract_no_ref {
144 ast::Expr::FieldExpr(_)
145 | ast::Expr::IndexExpr(_)
146 | ast::Expr::MacroExpr(_)
147 | ast::Expr::ParenExpr(_)
148 | ast::Expr::PathExpr(_) => true,
149 ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::UnaryOp::Deref) => {
150 to_extract_no_ref = prefix.expr()?;
151 needs_adjust = false;
152 false
153 }
154 _ => false,
155 };
156 let module = ctx.sema.scope(analysis.syntax())?.module();
157 let target = to_replace.start().text_range().cover(to_replace.end().text_range());
158 let needs_mut = match &parent {
159 Some(ast::Expr::RefExpr(expr)) => expr.mut_token().is_some(),
160 _ => needs_adjust && !needs_ref && ty.as_ref().is_some_and(|ty| ty.is_mutable_reference()),
161 };
162 for kind in ExtractionKind::ALL {
163 let Some(anchor) = Anchor::from(&place, kind) else {
164 continue;
165 };
166
167 let ty_string = match kind {
168 ExtractionKind::Constant | ExtractionKind::Static => {
169 let Some(ty) = ty.clone() else {
170 continue;
171 };
172
173 if needs_mut
176 || !is_body_const(&ctx.sema, &to_extract_no_ref)
177 || ty.is_unknown()
178 || ty.is_mutable_reference()
179 {
180 continue;
181 }
182
183 let Ok(type_string) = ty.display_source_code(ctx.db(), module.into(), false) else {
184 continue;
185 };
186
187 type_string
188 }
189 _ => "".to_owned(),
190 };
191
192 acc.add_group(
193 &GroupLabel("Extract into...".to_owned()),
194 kind.assist_id(),
195 kind.label(),
196 target,
197 |edit| {
198 let (var_name, expr_replace) = kind.get_name_and_expr(ctx, &analysis);
199
200 let to_replace =
201 if expr_replace.ancestors().last() == to_replace.start().ancestors().last() {
202 let element = expr_replace.clone().syntax_element();
203 element.clone()..=element
204 } else {
205 to_replace.clone()
206 };
207
208 let editor = edit.make_editor(&place);
209 let make = editor.make();
210
211 let pat_name = make.name(&var_name);
212 let name_expr = make.expr_path(make.ident_path(&var_name));
213
214 if let Some(cap) = ctx.config.snippet_cap {
215 let tabstop = edit.make_tabstop_before(cap);
216 editor.add_annotation(pat_name.syntax().clone(), tabstop);
217 }
218
219 let initializer = match ty.as_ref().filter(|_| needs_ref) {
220 Some(receiver_type) if receiver_type.is_mutable_reference() => {
221 make.expr_ref(to_extract_no_ref.clone(), true)
222 }
223 Some(receiver_type) if receiver_type.is_reference() => {
224 make.expr_ref(to_extract_no_ref.clone(), false)
225 }
226 _ => to_extract_no_ref.clone(),
227 };
228
229 let new_stmt: ast::Stmt = match kind {
230 ExtractionKind::Variable => {
231 let ident_pat = make.ident_pat(false, needs_mut, pat_name);
232 make.let_stmt(ident_pat.into(), None, Some(initializer)).into()
233 }
234 ExtractionKind::Constant => {
235 let ast_ty = make.ty(&ty_string);
236 ast::Item::Const(make.item_const(None, None, pat_name, ast_ty, initializer))
237 .into()
238 }
239 ExtractionKind::Static => {
240 let ast_ty = make.ty(&ty_string);
241 ast::Item::Static(make.item_static(
242 None,
243 false,
244 false,
245 pat_name,
246 ast_ty,
247 Some(initializer),
248 ))
249 .into()
250 }
251 };
252
253 match &anchor {
254 Anchor::Before(place) => {
255 let prev_ws = place.prev_sibling_or_token().and_then(|it| it.into_token());
256 let indent_to = IndentLevel::from_node(place);
257
258 let trailing_ws = if prev_ws.is_some_and(|it| it.text().starts_with('\n')) {
260 format!("\n{indent_to}")
261 } else {
262 " ".to_owned()
263 };
264
265 editor.insert_all(
266 Position::before(place),
267 vec![
268 new_stmt.syntax().clone().into(),
269 make.whitespace(&trailing_ws).into(),
270 ],
271 );
272
273 editor.replace_all(to_replace, vec![name_expr.syntax().syntax_element()]);
274 }
275 Anchor::Replace(stmt) => {
276 cov_mark::hit!(test_extract_var_expr_stmt);
277
278 editor.replace(stmt.syntax(), new_stmt.syntax());
279 }
280 Anchor::WrapInBlock(to_wrap) => {
281 let indent_to = to_wrap.indent_level();
282
283 let block = if to_wrap.syntax() == &expr_replace {
284 make.block_expr([new_stmt], Some(name_expr))
287 } else {
288 editor
290 .replace_all(to_replace, vec![name_expr.syntax().syntax_element()]);
291 make.block_expr([new_stmt], Some(to_wrap.clone()))
292 }
293 .indent_with_mapping(indent_to, make);
295
296 editor.replace(to_wrap.syntax(), block.syntax());
297 }
298 }
299 edit.add_file_edits(ctx.vfs_file_id(), editor);
300 edit.rename();
301 },
302 );
303 }
304
305 Some(())
306}
307
308fn extract_token_range_of(
309 node: &SyntaxNode,
310 range: TextRange,
311) -> Option<(SyntaxToken, SyntaxToken)> {
312 let first = node.token_at_offset(range.start()).right_biased()?;
313 let last = node.token_at_offset(range.end()).left_biased()?;
314
315 let first = skip_trivia_token(first, Direction::Next)?;
316 let last = skip_trivia_token(last, Direction::Next)?;
317
318 if first.text_range().ordering(last.text_range()).is_gt() {
319 return None;
320 }
321
322 Some((first, last))
323}
324
325fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
326 while let ast::Expr::ParenExpr(parens) = &expr {
327 let Some(expr_inside) = parens.expr() else { break };
328 expr = expr_inside;
329 }
330 expr
331}
332
333fn valid_target_expr(ctx: &AssistContext<'_, '_>) -> impl Fn(SyntaxNode) -> Option<ast::Expr> {
336 let selection = ctx.selection_trimmed();
337 move |node| match node.kind() {
338 SyntaxKind::LOOP_EXPR | SyntaxKind::LET_EXPR => None,
339 SyntaxKind::BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
340 SyntaxKind::RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
341 SyntaxKind::BLOCK_EXPR => {
342 ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
343 }
344 SyntaxKind::ARG_LIST => ast::ArgList::cast(node)?
345 .args()
346 .find(|expr| crate::utils::is_selected(expr, selection, false)),
347 SyntaxKind::PATH_EXPR => {
348 let path_expr = ast::PathExpr::cast(node)?;
349 let path_resolution = ctx.sema.resolve_path(&path_expr.path()?)?;
350 like_const_value(ctx, path_resolution).then_some(path_expr.into())
351 }
352 _ => ast::Expr::cast(node),
353 }
354}
355
356enum ExtractionKind {
357 Variable,
358 Constant,
359 Static,
360}
361
362impl ExtractionKind {
363 const ALL: &'static [ExtractionKind] =
364 &[ExtractionKind::Variable, ExtractionKind::Constant, ExtractionKind::Static];
365
366 fn assist_id(&self) -> AssistId {
367 let s = match self {
368 ExtractionKind::Variable => "extract_variable",
369 ExtractionKind::Constant => "extract_constant",
370 ExtractionKind::Static => "extract_static",
371 };
372
373 AssistId::refactor_extract(s)
374 }
375
376 fn label(&self) -> &'static str {
377 match self {
378 ExtractionKind::Variable => "Extract into variable",
379 ExtractionKind::Constant => "Extract into constant",
380 ExtractionKind::Static => "Extract into static",
381 }
382 }
383
384 fn get_name_and_expr(
385 &self,
386 ctx: &AssistContext<'_, '_>,
387 to_extract: &ast::Expr,
388 ) -> (String, SyntaxNode) {
389 if let ExtractionKind::Variable = self {
391 let field_shorthand = to_extract
392 .syntax()
393 .parent()
394 .and_then(ast::RecordExprField::cast)
395 .filter(|field| field.name_ref().is_some());
396
397 if let Some(field) = field_shorthand {
398 return (field.to_string(), field.syntax().clone());
399 }
400 }
401
402 let mut name_generator =
403 suggest_name::NameGenerator::new_from_scope_locals(ctx.sema.scope(to_extract.syntax()));
404 let var_name = if let Some(literal_name) = get_literal_name(ctx, to_extract) {
405 name_generator.suggest_name(&literal_name)
406 } else {
407 name_generator.for_variable(to_extract, &ctx.sema)
408 };
409
410 let var_name = match self {
411 ExtractionKind::Variable => var_name.to_lowercase(),
412 ExtractionKind::Constant | ExtractionKind::Static => var_name.to_uppercase(),
413 };
414
415 (var_name, to_extract.syntax().clone())
416 }
417}
418
419fn get_literal_name(ctx: &AssistContext<'_, '_>, expr: &ast::Expr) -> Option<String> {
420 let ast::Expr::Literal(literal) = expr else {
421 return None;
422 };
423
424 let inner = match literal.kind() {
425 ast::LiteralKind::String(string) => string.value().ok()?.into_owned(),
426 ast::LiteralKind::ByteString(byte_string) => {
427 String::from_utf8(byte_string.value().ok()?.into_owned()).ok()?
428 }
429 ast::LiteralKind::CString(cstring) => {
430 String::from_utf8(cstring.value().ok()?.into_owned()).ok()?
431 }
432 _ => return None,
433 };
434
435 if inner.len() > 32 {
437 return None;
438 }
439
440 match LexedStr::single_token(ctx.edition(), &inner) {
441 Some((SyntaxKind::IDENT, None)) => Some(inner),
442 _ => None,
443 }
444}
445
446#[derive(Debug, Clone)]
447enum Anchor {
448 Before(SyntaxNode),
449 Replace(ast::ExprStmt),
450 WrapInBlock(ast::Expr),
451}
452
453impl Anchor {
454 fn from(place: &SyntaxNode, kind: &ExtractionKind) -> Option<Anchor> {
455 let result = place
456 .ancestors()
457 .take_while(|it| !ast::Item::can_cast(it.kind()) || ast::MacroCall::can_cast(it.kind()))
458 .find_map(|node| {
459 if ast::MacroCall::can_cast(node.kind()) {
460 return None;
461 }
462 if let Some(expr) =
463 node.parent().and_then(ast::StmtList::cast).and_then(|it| it.tail_expr())
464 && expr.syntax() == &node
465 {
466 cov_mark::hit!(test_extract_var_last_expr);
467 return Some(Anchor::Before(node));
468 }
469
470 if let Some(parent) = node.parent() {
471 if let Some(parent) = ast::ClosureExpr::cast(parent.clone()) {
472 cov_mark::hit!(test_extract_var_in_closure_no_block);
473 return parent.body().map(Anchor::WrapInBlock);
474 }
475 if let Some(parent) = ast::MatchArm::cast(parent) {
476 if node.kind() == SyntaxKind::MATCH_GUARD {
477 cov_mark::hit!(test_extract_var_in_match_guard);
478 } else {
479 cov_mark::hit!(test_extract_var_in_match_arm_no_block);
480 return parent.expr().map(Anchor::WrapInBlock);
481 }
482 }
483 }
484
485 if let Some(stmt) = ast::Stmt::cast(node.clone()) {
486 if let ast::Stmt::ExprStmt(stmt) = stmt
487 && stmt.expr().is_some_and(|it| it.syntax() == place)
488 {
489 return Some(Anchor::Replace(stmt));
490 }
491 return Some(Anchor::Before(node));
492 }
493 None
494 });
495
496 match kind {
497 ExtractionKind::Constant | ExtractionKind::Static if result.is_none() => {
498 place.ancestors().find_map(|node| {
499 let item = ast::Item::cast(node.clone())?;
500 let parent = item.syntax().parent()?;
501 match parent.kind() {
502 SyntaxKind::ITEM_LIST
503 | SyntaxKind::SOURCE_FILE
504 | SyntaxKind::ASSOC_ITEM_LIST
505 | SyntaxKind::STMT_LIST => Some(Anchor::Before(node)),
506 _ => None,
507 }
508 })
509 }
510 _ => result,
511 }
512 }
513}
514
515fn like_const_value(ctx: &AssistContext<'_, '_>, path_resolution: hir::PathResolution) -> bool {
516 let db = ctx.db();
517 let adt_like_const_value = |adt: Option<hir::Adt>| matches!(adt, Some(hir::Adt::Struct(s)) if s.kind(db) == hir::StructKind::Unit);
518 match path_resolution {
519 hir::PathResolution::Def(def) => match def {
520 hir::ModuleDef::Adt(adt) => adt_like_const_value(Some(adt)),
521 hir::ModuleDef::EnumVariant(variant) => variant.kind(db) == hir::StructKind::Unit,
522 hir::ModuleDef::TypeAlias(ty) => adt_like_const_value(ty.ty(db).as_adt()),
523 hir::ModuleDef::Const(_) | hir::ModuleDef::Static(_) => true,
524 hir::ModuleDef::Trait(_)
525 | hir::ModuleDef::BuiltinType(_)
526 | hir::ModuleDef::Macro(_)
527 | hir::ModuleDef::Module(_) => false,
528 hir::ModuleDef::Function(_) => false, },
530 hir::PathResolution::SelfType(ty) => adt_like_const_value(ty.self_ty(db).as_adt()),
531 hir::PathResolution::ConstParam(_) => true,
532 hir::PathResolution::Local(_)
533 | hir::PathResolution::TypeParam(_)
534 | hir::PathResolution::BuiltinAttr(_)
535 | hir::PathResolution::ToolModule(_)
536 | hir::PathResolution::DeriveHelper(_) => false,
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use crate::tests::{
546 check_assist_by_label, check_assist_not_applicable, check_assist_not_applicable_by_label,
547 check_assist_target,
548 };
549
550 use super::*;
551
552 #[test]
553 fn extract_var_simple_without_select() {
554 check_assist_by_label(
555 extract_variable,
556 r#"
557fn main() -> i32 {
558 if$0 true {
559 1
560 } else {
561 2
562 }
563}
564"#,
565 r#"
566fn main() -> i32 {
567 let $0var_name = if true {
568 1
569 } else {
570 2
571 };
572 var_name
573}
574"#,
575 "Extract into variable",
576 );
577
578 check_assist_by_label(
579 extract_variable,
580 r#"
581fn foo() -> i32 { 1 }
582fn main() {
583 foo();$0
584}
585"#,
586 r#"
587fn foo() -> i32 { 1 }
588fn main() {
589 let $0foo = foo();
590}
591"#,
592 "Extract into variable",
593 );
594
595 check_assist_by_label(
596 extract_variable,
597 r#"
598fn main() {
599 let a = Some(2);
600 a.is_some();$0
601}
602"#,
603 r#"
604fn main() {
605 let a = Some(2);
606 let $0is_some = a.is_some();
607}
608"#,
609 "Extract into variable",
610 );
611
612 check_assist_by_label(
613 extract_variable,
614 r#"
615fn main() {
616 "hello"$0;
617}
618"#,
619 r#"
620fn main() {
621 let $0hello = "hello";
622}
623"#,
624 "Extract into variable",
625 );
626
627 check_assist_by_label(
628 extract_variable,
629 r#"
630fn main() {
631 1 + 2$0;
632}
633"#,
634 r#"
635fn main() {
636 let $0var_name = 1 + 2;
637}
638"#,
639 "Extract into variable",
640 );
641
642 check_assist_by_label(
643 extract_variable,
644 r#"
645fn main() {
646 match () {
647 () if true => 1,
648 _ => 2,
649 };$0
650}
651"#,
652 r#"
653fn main() {
654 let $0var_name = match () {
655 () if true => 1,
656 _ => 2,
657 };
658}
659"#,
660 "Extract into variable",
661 );
662 }
663
664 #[test]
665 fn extract_const_simple_without_select() {
666 check_assist_by_label(
667 extract_variable,
668 r#"
669fn main() -> i32 {
670 if$0 true {
671 1
672 } else {
673 2
674 }
675}
676"#,
677 r#"
678fn main() -> i32 {
679 const $0VAR_NAME: i32 = if true {
680 1
681 } else {
682 2
683 };
684 VAR_NAME
685}
686"#,
687 "Extract into constant",
688 );
689
690 check_assist_by_label(
691 extract_variable,
692 r#"
693const fn foo() -> i32 { 1 }
694fn main() {
695 foo();$0
696}
697"#,
698 r#"
699const fn foo() -> i32 { 1 }
700fn main() {
701 const $0FOO: i32 = foo();
702}
703"#,
704 "Extract into constant",
705 );
706
707 check_assist_by_label(
708 extract_variable,
709 r#"
710fn main() {
711 "hello"$0;
712}
713"#,
714 r#"
715fn main() {
716 const $0HELLO: &'static str = "hello";
717}
718"#,
719 "Extract into constant",
720 );
721
722 check_assist_by_label(
723 extract_variable,
724 r#"
725fn main() {
726 1 + 2$0;
727}
728"#,
729 r#"
730fn main() {
731 const $0VAR_NAME: i32 = 1 + 2;
732}
733"#,
734 "Extract into constant",
735 );
736
737 check_assist_by_label(
738 extract_variable,
739 r#"
740fn main() {
741 match () {
742 () if true => 1,
743 _ => 2,
744 };$0
745}
746"#,
747 r#"
748fn main() {
749 const $0VAR_NAME: i32 = match () {
750 () if true => 1,
751 _ => 2,
752 };
753}
754"#,
755 "Extract into constant",
756 );
757 }
758
759 #[test]
760 fn extract_static_simple_without_select() {
761 check_assist_by_label(
762 extract_variable,
763 r#"
764fn main() -> i32 {
765 if$0 true {
766 1
767 } else {
768 2
769 }
770}
771"#,
772 r#"
773fn main() -> i32 {
774 static $0VAR_NAME: i32 = if true {
775 1
776 } else {
777 2
778 };
779 VAR_NAME
780}
781"#,
782 "Extract into static",
783 );
784
785 check_assist_by_label(
786 extract_variable,
787 r#"
788const fn foo() -> i32 { 1 }
789fn main() {
790 foo();$0
791}
792"#,
793 r#"
794const fn foo() -> i32 { 1 }
795fn main() {
796 static $0FOO: i32 = foo();
797}
798"#,
799 "Extract into static",
800 );
801
802 check_assist_by_label(
803 extract_variable,
804 r#"
805fn main() {
806 "hello"$0;
807}
808"#,
809 r#"
810fn main() {
811 static $0HELLO: &'static str = "hello";
812}
813"#,
814 "Extract into static",
815 );
816
817 check_assist_by_label(
818 extract_variable,
819 r#"
820fn main() {
821 1 + 2$0;
822}
823"#,
824 r#"
825fn main() {
826 static $0VAR_NAME: i32 = 1 + 2;
827}
828"#,
829 "Extract into static",
830 );
831
832 check_assist_by_label(
833 extract_variable,
834 r#"
835fn main() {
836 match () {
837 () if true => 1,
838 _ => 2,
839 };$0
840}
841"#,
842 r#"
843fn main() {
844 static $0VAR_NAME: i32 = match () {
845 () if true => 1,
846 _ => 2,
847 };
848}
849"#,
850 "Extract into static",
851 );
852 }
853
854 #[test]
855 fn dont_extract_unit_expr_without_select() {
856 check_assist_not_applicable(
857 extract_variable,
858 r#"
859fn foo() {}
860fn main() {
861 foo()$0;
862}
863"#,
864 );
865
866 check_assist_not_applicable(
867 extract_variable,
868 r#"
869fn foo() {
870 let mut i = 3;
871 if i >= 0 {
872 i += 1;
873 } else {
874 i -= 1;
875 }$0
876}"#,
877 );
878 }
879
880 #[test]
881 fn extract_var_simple() {
882 check_assist_by_label(
883 extract_variable,
884 r#"
885fn foo() {
886 foo($01 + 1$0);
887}"#,
888 r#"
889fn foo() {
890 let $0var_name = 1 + 1;
891 foo(var_name);
892}"#,
893 "Extract into variable",
894 );
895 }
896
897 #[test]
898 fn extract_const_simple() {
899 check_assist_by_label(
900 extract_variable,
901 r#"
902fn foo() {
903 foo($01 + 1$0);
904}"#,
905 r#"
906fn foo() {
907 const $0VAR_NAME: i32 = 1 + 1;
908 foo(VAR_NAME);
909}"#,
910 "Extract into constant",
911 );
912 }
913
914 #[test]
915 fn extract_static_simple() {
916 check_assist_by_label(
917 extract_variable,
918 r#"
919fn foo() {
920 foo($01 + 1$0);
921}"#,
922 r#"
923fn foo() {
924 static $0VAR_NAME: i32 = 1 + 1;
925 foo(VAR_NAME);
926}"#,
927 "Extract into static",
928 );
929 }
930
931 #[test]
932 fn dont_extract_in_comment() {
933 cov_mark::check!(extract_var_in_comment_is_not_applicable);
934 check_assist_not_applicable(extract_variable, r#"fn main() { 1 + /* $0comment$0 */ 1; }"#);
935 }
936
937 #[test]
938 fn extract_var_expr_stmt() {
939 cov_mark::check!(test_extract_var_expr_stmt);
940 check_assist_by_label(
941 extract_variable,
942 r#"
943fn foo() {
944 $0 1 + 1$0;
945}"#,
946 r#"
947fn foo() {
948 let $0var_name = 1 + 1;
949}"#,
950 "Extract into variable",
951 );
952 check_assist_by_label(
953 extract_variable,
954 r#"
955fn foo() {
956 $0{ let x = 0; x }$0;
957 something_else();
958}"#,
959 r#"
960fn foo() {
961 let $0var_name = { let x = 0; x };
962 something_else();
963}"#,
964 "Extract into variable",
965 );
966 }
967
968 #[test]
969 fn extract_const_expr_stmt() {
970 cov_mark::check!(test_extract_var_expr_stmt);
971 check_assist_by_label(
972 extract_variable,
973 r#"
974fn foo() {
975 $0 1 + 1$0;
976}"#,
977 r#"
978fn foo() {
979 const $0VAR_NAME: i32 = 1 + 1;
980}"#,
981 "Extract into constant",
982 );
983 check_assist_by_label(
985 extract_variable,
986 r#"
987fn foo() {
988 $0{ let x = 0; x }$0;
989 something_else();
990}"#,
991 r#"
992fn foo() {
993 const $0VAR_NAME: i32 = { let x = 0; x };
994 something_else();
995}"#,
996 "Extract into constant",
997 );
998 }
999
1000 #[test]
1001 fn extract_static_expr_stmt() {
1002 cov_mark::check!(test_extract_var_expr_stmt);
1003 check_assist_by_label(
1004 extract_variable,
1005 r#"
1006fn foo() {
1007 $0 1 + 1$0;
1008}"#,
1009 r#"
1010fn foo() {
1011 static $0VAR_NAME: i32 = 1 + 1;
1012}"#,
1013 "Extract into static",
1014 );
1015 check_assist_by_label(
1017 extract_variable,
1018 r#"
1019fn foo() {
1020 $0{ let x = 0; x }$0;
1021 something_else();
1022}"#,
1023 r#"
1024fn foo() {
1025 static $0VAR_NAME: i32 = { let x = 0; x };
1026 something_else();
1027}"#,
1028 "Extract into static",
1029 );
1030 }
1031
1032 #[test]
1033 fn extract_var_part_of_expr_stmt() {
1034 check_assist_by_label(
1035 extract_variable,
1036 r#"
1037fn foo() {
1038 $01$0 + 1;
1039}"#,
1040 r#"
1041fn foo() {
1042 let $0var_name = 1;
1043 var_name + 1;
1044}"#,
1045 "Extract into variable",
1046 );
1047 }
1048
1049 #[test]
1050 fn extract_const_part_of_expr_stmt() {
1051 check_assist_by_label(
1052 extract_variable,
1053 r#"
1054fn foo() {
1055 $01$0 + 1;
1056}"#,
1057 r#"
1058fn foo() {
1059 const $0VAR_NAME: i32 = 1;
1060 VAR_NAME + 1;
1061}"#,
1062 "Extract into constant",
1063 );
1064 }
1065
1066 #[test]
1067 fn extract_static_part_of_expr_stmt() {
1068 check_assist_by_label(
1069 extract_variable,
1070 r#"
1071fn foo() {
1072 $01$0 + 1;
1073}"#,
1074 r#"
1075fn foo() {
1076 static $0VAR_NAME: i32 = 1;
1077 VAR_NAME + 1;
1078}"#,
1079 "Extract into static",
1080 );
1081 }
1082
1083 #[test]
1084 fn extract_var_last_expr() {
1085 cov_mark::check!(test_extract_var_last_expr);
1086 check_assist_by_label(
1087 extract_variable,
1088 r#"
1089fn foo() {
1090 bar($01 + 1$0)
1091}
1092"#,
1093 r#"
1094fn foo() {
1095 let $0var_name = 1 + 1;
1096 bar(var_name)
1097}
1098"#,
1099 "Extract into variable",
1100 );
1101 check_assist_by_label(
1102 extract_variable,
1103 r#"
1104fn foo() -> i32 {
1105 $0bar(1 + 1)$0
1106}
1107
1108fn bar(i: i32) -> i32 {
1109 i
1110}
1111"#,
1112 r#"
1113fn foo() -> i32 {
1114 let $0bar = bar(1 + 1);
1115 bar
1116}
1117
1118fn bar(i: i32) -> i32 {
1119 i
1120}
1121"#,
1122 "Extract into variable",
1123 )
1124 }
1125
1126 #[test]
1127 fn extract_const_last_expr() {
1128 cov_mark::check!(test_extract_var_last_expr);
1129 check_assist_by_label(
1130 extract_variable,
1131 r#"
1132fn foo() {
1133 bar($01 + 1$0)
1134}
1135"#,
1136 r#"
1137fn foo() {
1138 const $0VAR_NAME: i32 = 1 + 1;
1139 bar(VAR_NAME)
1140}
1141"#,
1142 "Extract into constant",
1143 );
1144 check_assist_by_label(
1145 extract_variable,
1146 r#"
1147fn foo() -> i32 {
1148 $0bar(1 + 1)$0
1149}
1150
1151const fn bar(i: i32) -> i32 {
1152 i
1153}
1154"#,
1155 r#"
1156fn foo() -> i32 {
1157 const $0BAR: i32 = bar(1 + 1);
1158 BAR
1159}
1160
1161const fn bar(i: i32) -> i32 {
1162 i
1163}
1164"#,
1165 "Extract into constant",
1166 )
1167 }
1168
1169 #[test]
1170 fn extract_static_last_expr() {
1171 cov_mark::check!(test_extract_var_last_expr);
1172 check_assist_by_label(
1173 extract_variable,
1174 r#"
1175fn foo() {
1176 bar($01 + 1$0)
1177}
1178"#,
1179 r#"
1180fn foo() {
1181 static $0VAR_NAME: i32 = 1 + 1;
1182 bar(VAR_NAME)
1183}
1184"#,
1185 "Extract into static",
1186 );
1187 check_assist_by_label(
1188 extract_variable,
1189 r#"
1190fn foo() -> i32 {
1191 $0bar(1 + 1)$0
1192}
1193
1194const fn bar(i: i32) -> i32 {
1195 i
1196}
1197"#,
1198 r#"
1199fn foo() -> i32 {
1200 static $0BAR: i32 = bar(1 + 1);
1201 BAR
1202}
1203
1204const fn bar(i: i32) -> i32 {
1205 i
1206}
1207"#,
1208 "Extract into static",
1209 )
1210 }
1211
1212 #[test]
1213 fn extract_var_in_match_arm_no_block() {
1214 cov_mark::check!(test_extract_var_in_match_arm_no_block);
1215 check_assist_by_label(
1216 extract_variable,
1217 r#"
1218fn main() {
1219 let x = true;
1220 let tuple = match x {
1221 true => ($02 + 2$0, true)
1222 _ => (0, false)
1223 };
1224}
1225"#,
1226 r#"
1227fn main() {
1228 let x = true;
1229 let tuple = match x {
1230 true => {
1231 let $0var_name = 2 + 2;
1232 (var_name, true)
1233 }
1234 _ => (0, false)
1235 };
1236}
1237"#,
1238 "Extract into variable",
1239 );
1240 }
1241
1242 #[test]
1243 fn extract_var_in_match_arm_with_block() {
1244 check_assist_by_label(
1245 extract_variable,
1246 r#"
1247fn main() {
1248 let x = true;
1249 let tuple = match x {
1250 true => {
1251 let y = 1;
1252 ($02 + y$0, true)
1253 }
1254 _ => (0, false)
1255 };
1256}
1257"#,
1258 r#"
1259fn main() {
1260 let x = true;
1261 let tuple = match x {
1262 true => {
1263 let y = 1;
1264 let $0var_name = 2 + y;
1265 (var_name, true)
1266 }
1267 _ => (0, false)
1268 };
1269}
1270"#,
1271 "Extract into variable",
1272 );
1273 }
1274
1275 #[test]
1276 fn extract_var_in_match_guard() {
1277 cov_mark::check!(test_extract_var_in_match_guard);
1278 check_assist_by_label(
1279 extract_variable,
1280 r#"
1281fn main() {
1282 match () {
1283 () if $010 > 0$0 => 1
1284 _ => 2
1285 };
1286}
1287"#,
1288 r#"
1289fn main() {
1290 let $0var_name = 10 > 0;
1291 match () {
1292 () if var_name => 1
1293 _ => 2
1294 };
1295}
1296"#,
1297 "Extract into variable",
1298 );
1299 }
1300
1301 #[test]
1302 fn extract_var_in_closure_no_block() {
1303 cov_mark::check!(test_extract_var_in_closure_no_block);
1304 check_assist_by_label(
1305 extract_variable,
1306 r#"
1307fn main() {
1308 let lambda = |x: u32| $0x * 2$0;
1309}
1310"#,
1311 r#"
1312fn main() {
1313 let lambda = |x: u32| {
1314 let $0var_name = x * 2;
1315 var_name
1316 };
1317}
1318"#,
1319 "Extract into variable",
1320 );
1321 }
1322
1323 #[test]
1324 fn extract_var_in_closure_with_block() {
1325 check_assist_by_label(
1326 extract_variable,
1327 r#"
1328fn main() {
1329 let lambda = |x: u32| { $0x * 2$0 };
1330}
1331"#,
1332 r#"
1333fn main() {
1334 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
1335}
1336"#,
1337 "Extract into variable",
1338 );
1339 }
1340
1341 #[test]
1342 fn extract_var_in_arglist_with_comma() {
1343 check_assist_by_label(
1344 extract_variable,
1345 r#"
1346fn main() {
1347 let x = 2;
1348 foo(
1349 x + x,
1350 $0x - x,$0
1351 )
1352}
1353"#,
1354 r#"
1355fn main() {
1356 let x = 2;
1357 let $0var_name = x - x;
1358 foo(
1359 x + x,
1360 var_name,
1361 )
1362}
1363"#,
1364 "Extract into variable",
1365 );
1366 }
1367
1368 #[test]
1369 fn extract_var_path_simple() {
1370 check_assist_by_label(
1371 extract_variable,
1372 r#"
1373fn main() {
1374 let o = $0Some(true)$0;
1375}
1376"#,
1377 r#"
1378fn main() {
1379 let $0var_name = Some(true);
1380 let o = var_name;
1381}
1382"#,
1383 "Extract into variable",
1384 );
1385 }
1386
1387 #[test]
1388 fn extract_var_path_method() {
1389 check_assist_by_label(
1390 extract_variable,
1391 r#"
1392fn main() {
1393 let v = $0bar.foo()$0;
1394}
1395"#,
1396 r#"
1397fn main() {
1398 let $0foo = bar.foo();
1399 let v = foo;
1400}
1401"#,
1402 "Extract into variable",
1403 );
1404 }
1405
1406 #[test]
1407 fn extract_var_return() {
1408 check_assist_by_label(
1409 extract_variable,
1410 r#"
1411fn foo() -> u32 {
1412 $0return 2 + 2$0;
1413}
1414"#,
1415 r#"
1416fn foo() -> u32 {
1417 let $0var_name = 2 + 2;
1418 return var_name;
1419}
1420"#,
1421 "Extract into variable",
1422 );
1423 }
1424
1425 #[test]
1426 fn extract_var_does_not_add_extra_whitespace() {
1427 check_assist_by_label(
1428 extract_variable,
1429 r#"
1430fn foo() -> u32 {
1431
1432
1433 $0return 2 + 2$0;
1434}
1435"#,
1436 r#"
1437fn foo() -> u32 {
1438
1439
1440 let $0var_name = 2 + 2;
1441 return var_name;
1442}
1443"#,
1444 "Extract into variable",
1445 );
1446
1447 check_assist_by_label(
1448 extract_variable,
1449 r#"
1450fn foo() -> u32 {
1451
1452 $0return 2 + 2$0;
1453}
1454"#,
1455 r#"
1456fn foo() -> u32 {
1457
1458 let $0var_name = 2 + 2;
1459 return var_name;
1460}
1461"#,
1462 "Extract into variable",
1463 );
1464
1465 check_assist_by_label(
1466 extract_variable,
1467 r#"
1468fn foo() -> u32 {
1469 let foo = 1;
1470
1471 // bar
1472
1473
1474 $0return 2 + 2$0;
1475}
1476"#,
1477 r#"
1478fn foo() -> u32 {
1479 let foo = 1;
1480
1481 // bar
1482
1483
1484 let $0var_name = 2 + 2;
1485 return var_name;
1486}
1487"#,
1488 "Extract into variable",
1489 );
1490 }
1491
1492 #[test]
1493 fn extract_var_break() {
1494 check_assist_by_label(
1495 extract_variable,
1496 r#"
1497fn main() {
1498 let result = loop {
1499 $0break 2 + 2$0;
1500 };
1501}
1502"#,
1503 r#"
1504fn main() {
1505 let result = loop {
1506 let $0var_name = 2 + 2;
1507 break var_name;
1508 };
1509}
1510"#,
1511 "Extract into variable",
1512 );
1513 }
1514
1515 #[test]
1516 fn extract_var_let_expr() {
1517 check_assist_by_label(
1518 extract_variable,
1519 r#"
1520fn main() {
1521 if $0let$0 Some(x) = Some(2+2) {}
1522}
1523"#,
1524 r#"
1525fn main() {
1526 let $0var_name = Some(2+2);
1527 if let Some(x) = var_name {}
1528}
1529"#,
1530 "Extract into variable",
1531 );
1532 }
1533
1534 #[test]
1535 fn extract_var_for_cast() {
1536 check_assist_by_label(
1537 extract_variable,
1538 r#"
1539fn main() {
1540 let v = $00f32 as u32$0;
1541}
1542"#,
1543 r#"
1544fn main() {
1545 let $0var_name = 0f32 as u32;
1546 let v = var_name;
1547}
1548"#,
1549 "Extract into variable",
1550 );
1551 }
1552
1553 #[test]
1554 fn extract_var_field_shorthand() {
1555 check_assist_by_label(
1556 extract_variable,
1557 r#"
1558struct S {
1559 foo: i32
1560}
1561
1562fn main() {
1563 S { foo: $01 + 1$0 }
1564}
1565"#,
1566 r#"
1567struct S {
1568 foo: i32
1569}
1570
1571fn main() {
1572 let $0foo = 1 + 1;
1573 S { foo }
1574}
1575"#,
1576 "Extract into variable",
1577 )
1578 }
1579
1580 #[test]
1581 fn extract_var_name_from_type() {
1582 check_assist_by_label(
1583 extract_variable,
1584 r#"
1585struct Test(i32);
1586
1587fn foo() -> Test {
1588 $0{ Test(10) }$0
1589}
1590"#,
1591 r#"
1592struct Test(i32);
1593
1594fn foo() -> Test {
1595 let $0test = { Test(10) };
1596 test
1597}
1598"#,
1599 "Extract into variable",
1600 )
1601 }
1602
1603 #[test]
1604 fn extract_var_name_from_parameter() {
1605 check_assist_by_label(
1606 extract_variable,
1607 r#"
1608fn bar(test: u32, size: u32)
1609
1610fn foo() {
1611 bar(1, $01+1$0);
1612}
1613"#,
1614 r#"
1615fn bar(test: u32, size: u32)
1616
1617fn foo() {
1618 let $0size = 1+1;
1619 bar(1, size);
1620}
1621"#,
1622 "Extract into variable",
1623 )
1624 }
1625
1626 #[test]
1627 fn extract_var_parameter_name_has_precedence_over_type() {
1628 check_assist_by_label(
1629 extract_variable,
1630 r#"
1631struct TextSize(u32);
1632fn bar(test: u32, size: TextSize)
1633
1634fn foo() {
1635 bar(1, $0{ TextSize(1+1) }$0);
1636}
1637"#,
1638 r#"
1639struct TextSize(u32);
1640fn bar(test: u32, size: TextSize)
1641
1642fn foo() {
1643 let $0size = { TextSize(1+1) };
1644 bar(1, size);
1645}
1646"#,
1647 "Extract into variable",
1648 )
1649 }
1650
1651 #[test]
1652 fn extract_var_name_from_function() {
1653 check_assist_by_label(
1654 extract_variable,
1655 r#"
1656fn is_required(test: u32, size: u32) -> bool
1657
1658fn foo() -> bool {
1659 $0is_required(1, 2)$0
1660}
1661"#,
1662 r#"
1663fn is_required(test: u32, size: u32) -> bool
1664
1665fn foo() -> bool {
1666 let $0is_required = is_required(1, 2);
1667 is_required
1668}
1669"#,
1670 "Extract into variable",
1671 )
1672 }
1673
1674 #[test]
1675 fn extract_var_name_from_method() {
1676 check_assist_by_label(
1677 extract_variable,
1678 r#"
1679struct S;
1680impl S {
1681 fn bar(&self, n: u32) -> u32 { n }
1682}
1683
1684fn foo() -> u32 {
1685 $0S.bar(1)$0
1686}
1687"#,
1688 r#"
1689struct S;
1690impl S {
1691 fn bar(&self, n: u32) -> u32 { n }
1692}
1693
1694fn foo() -> u32 {
1695 let $0bar = S.bar(1);
1696 bar
1697}
1698"#,
1699 "Extract into variable",
1700 )
1701 }
1702
1703 #[test]
1704 fn extract_var_name_from_method_param() {
1705 check_assist_by_label(
1706 extract_variable,
1707 r#"
1708struct S;
1709impl S {
1710 fn bar(&self, n: u32, size: u32) { n }
1711}
1712
1713fn foo() {
1714 S.bar($01 + 1$0, 2)
1715}
1716"#,
1717 r#"
1718struct S;
1719impl S {
1720 fn bar(&self, n: u32, size: u32) { n }
1721}
1722
1723fn foo() {
1724 let $0n = 1 + 1;
1725 S.bar(n, 2)
1726}
1727"#,
1728 "Extract into variable",
1729 )
1730 }
1731
1732 #[test]
1733 fn extract_var_name_from_ufcs_method_param() {
1734 check_assist_by_label(
1735 extract_variable,
1736 r#"
1737struct S;
1738impl S {
1739 fn bar(&self, n: u32, size: u32) { n }
1740}
1741
1742fn foo() {
1743 S::bar(&S, $01 + 1$0, 2)
1744}
1745"#,
1746 r#"
1747struct S;
1748impl S {
1749 fn bar(&self, n: u32, size: u32) { n }
1750}
1751
1752fn foo() {
1753 let $0n = 1 + 1;
1754 S::bar(&S, n, 2)
1755}
1756"#,
1757 "Extract into variable",
1758 )
1759 }
1760
1761 #[test]
1762 fn extract_var_parameter_name_has_precedence_over_function() {
1763 check_assist_by_label(
1764 extract_variable,
1765 r#"
1766fn bar(test: u32, size: u32)
1767
1768fn foo() {
1769 bar(1, $0symbol_size(1, 2)$0);
1770}
1771"#,
1772 r#"
1773fn bar(test: u32, size: u32)
1774
1775fn foo() {
1776 let $0size = symbol_size(1, 2);
1777 bar(1, size);
1778}
1779"#,
1780 "Extract into variable",
1781 )
1782 }
1783
1784 #[test]
1785 fn extract_macro_call() {
1786 check_assist_by_label(
1787 extract_variable,
1788 r#"
1789struct Vec;
1790macro_rules! vec {
1791 () => {Vec}
1792}
1793fn main() {
1794 let _ = $0vec![]$0;
1795}
1796"#,
1797 r#"
1798struct Vec;
1799macro_rules! vec {
1800 () => {Vec}
1801}
1802fn main() {
1803 let $0items = vec![];
1804 let _ = items;
1805}
1806"#,
1807 "Extract into variable",
1808 );
1809
1810 check_assist_by_label(
1811 extract_variable,
1812 r#"
1813struct Vec;
1814macro_rules! vec {
1815 () => {Vec}
1816}
1817fn main() {
1818 let _ = $0vec![]$0;
1819}
1820"#,
1821 r#"
1822struct Vec;
1823macro_rules! vec {
1824 () => {Vec}
1825}
1826fn main() {
1827 const $0ITEMS: Vec = vec![];
1828 let _ = ITEMS;
1829}
1830"#,
1831 "Extract into constant",
1832 );
1833
1834 check_assist_by_label(
1835 extract_variable,
1836 r#"
1837struct Vec;
1838macro_rules! vec {
1839 () => {Vec}
1840}
1841fn main() {
1842 let _ = $0vec![]$0;
1843}
1844"#,
1845 r#"
1846struct Vec;
1847macro_rules! vec {
1848 () => {Vec}
1849}
1850fn main() {
1851 static $0ITEMS: Vec = vec![];
1852 let _ = ITEMS;
1853}
1854"#,
1855 "Extract into static",
1856 );
1857 }
1858
1859 #[test]
1860 fn extract_non_local_path_expr() {
1861 check_assist_by_label(
1862 extract_variable,
1863 r#"
1864struct Foo;
1865fn foo() -> Foo {
1866 $0Foo$0
1867}
1868"#,
1869 r#"
1870struct Foo;
1871fn foo() -> Foo {
1872 let $0foo = Foo;
1873 foo
1874}
1875"#,
1876 "Extract into variable",
1877 );
1878 }
1879
1880 #[test]
1881 fn extract_var_for_return_not_applicable() {
1882 check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");
1883 }
1884
1885 #[test]
1886 fn extract_var_for_break_not_applicable() {
1887 check_assist_not_applicable(extract_variable, "fn main() { loop { $0break$0; }; }");
1888 }
1889
1890 #[test]
1891 fn extract_var_for_let_expr_not_applicable() {
1892 check_assist_not_applicable(
1893 extract_variable,
1894 "fn main() { if $0let Some(x) = Some(2+2) {} }",
1895 );
1896 }
1897
1898 #[test]
1899 fn extract_var_unit_expr_not_applicable() {
1900 check_assist_not_applicable(
1901 extract_variable,
1902 r#"
1903fn foo() {
1904 let mut i = 3;
1905 $0if i >= 0 {
1906 i += 1;
1907 } else {
1908 i -= 1;
1909 }$0
1910}"#,
1911 );
1912 }
1913
1914 #[test]
1916 fn extract_var_target() {
1917 check_assist_target(extract_variable, r#"fn foo() -> u32 { $0return 2 + 2$0; }"#, "2 + 2");
1918
1919 check_assist_target(
1920 extract_variable,
1921 r#"
1922fn main() {
1923 let x = true;
1924 let tuple = match x {
1925 true => ($02 + 2$0, true)
1926 _ => (0, false)
1927 };
1928}
1929"#,
1930 "2 + 2",
1931 );
1932 }
1933
1934 #[test]
1935 fn extract_var_no_block_body() {
1936 check_assist_not_applicable_by_label(
1937 extract_variable,
1938 r#"
1939const X: usize = $0100$0;
1940"#,
1941 "Extract into variable",
1942 );
1943 }
1944
1945 #[test]
1946 fn extract_const_no_block_body() {
1947 check_assist_by_label(
1948 extract_variable,
1949 r#"
1950const fn foo(x: i32) -> i32 {
1951 x
1952}
1953
1954const FOO: i32 = foo($0100$0);
1955"#,
1956 r#"
1957const fn foo(x: i32) -> i32 {
1958 x
1959}
1960
1961const $0X: i32 = 100;
1962const FOO: i32 = foo(X);
1963"#,
1964 "Extract into constant",
1965 );
1966
1967 check_assist_by_label(
1968 extract_variable,
1969 r#"
1970mod foo {
1971 enum Foo {
1972 Bar,
1973 Baz = $042$0,
1974 }
1975}
1976"#,
1977 r#"
1978mod foo {
1979 const $0VAR_NAME: isize = 42;
1980 enum Foo {
1981 Bar,
1982 Baz = VAR_NAME,
1983 }
1984}
1985"#,
1986 "Extract into constant",
1987 );
1988
1989 check_assist_by_label(
1990 extract_variable,
1991 r#"
1992const fn foo(x: i32) -> i32 {
1993 x
1994}
1995
1996trait Hello {
1997 const World: i32;
1998}
1999
2000struct Bar;
2001impl Hello for Bar {
2002 const World = foo($042$0);
2003}
2004"#,
2005 r#"
2006const fn foo(x: i32) -> i32 {
2007 x
2008}
2009
2010trait Hello {
2011 const World: i32;
2012}
2013
2014struct Bar;
2015impl Hello for Bar {
2016 const $0X: i32 = 42;
2017 const World = foo(X);
2018}
2019"#,
2020 "Extract into constant",
2021 );
2022
2023 check_assist_by_label(
2024 extract_variable,
2025 r#"
2026const fn foo(x: i32) -> i32 {
2027 x
2028}
2029
2030fn bar() {
2031 const BAZ: i32 = foo($042$0);
2032}
2033"#,
2034 r#"
2035const fn foo(x: i32) -> i32 {
2036 x
2037}
2038
2039fn bar() {
2040 const $0X: i32 = 42;
2041 const BAZ: i32 = foo(X);
2042}
2043"#,
2044 "Extract into constant",
2045 );
2046 }
2047
2048 #[test]
2049 fn extract_static_no_block_body() {
2050 check_assist_by_label(
2051 extract_variable,
2052 r#"
2053const fn foo(x: i32) -> i32 {
2054 x
2055}
2056
2057const FOO: i32 = foo($0100$0);
2058"#,
2059 r#"
2060const fn foo(x: i32) -> i32 {
2061 x
2062}
2063
2064static $0X: i32 = 100;
2065const FOO: i32 = foo(X);
2066"#,
2067 "Extract into static",
2068 );
2069
2070 check_assist_by_label(
2071 extract_variable,
2072 r#"
2073mod foo {
2074 enum Foo {
2075 Bar,
2076 Baz = $042$0,
2077 }
2078}
2079"#,
2080 r#"
2081mod foo {
2082 static $0VAR_NAME: isize = 42;
2083 enum Foo {
2084 Bar,
2085 Baz = VAR_NAME,
2086 }
2087}
2088"#,
2089 "Extract into static",
2090 );
2091
2092 check_assist_by_label(
2093 extract_variable,
2094 r#"
2095const fn foo(x: i32) -> i32 {
2096 x
2097}
2098
2099trait Hello {
2100 const World: i32;
2101}
2102
2103struct Bar;
2104impl Hello for Bar {
2105 const World = foo($042$0);
2106}
2107"#,
2108 r#"
2109const fn foo(x: i32) -> i32 {
2110 x
2111}
2112
2113trait Hello {
2114 const World: i32;
2115}
2116
2117struct Bar;
2118impl Hello for Bar {
2119 static $0X: i32 = 42;
2120 const World = foo(X);
2121}
2122"#,
2123 "Extract into static",
2124 );
2125
2126 check_assist_by_label(
2127 extract_variable,
2128 r#"
2129const fn foo(x: i32) -> i32 {
2130 x
2131}
2132
2133fn bar() {
2134 const BAZ: i32 = foo($042$0);
2135}
2136"#,
2137 r#"
2138const fn foo(x: i32) -> i32 {
2139 x
2140}
2141
2142fn bar() {
2143 static $0X: i32 = 42;
2144 const BAZ: i32 = foo(X);
2145}
2146"#,
2147 "Extract into static",
2148 );
2149 }
2150
2151 #[test]
2152 fn extract_var_mutable_reference_parameter() {
2153 check_assist_by_label(
2154 extract_variable,
2155 r#"
2156struct S {
2157 vec: Vec<u8>
2158}
2159
2160struct Vec<T>;
2161impl<T> Vec<T> {
2162 fn push(&mut self, _:usize) {}
2163}
2164
2165fn foo(s: &mut S) {
2166 $0s.vec$0.push(0);
2167}"#,
2168 r#"
2169struct S {
2170 vec: Vec<u8>
2171}
2172
2173struct Vec<T>;
2174impl<T> Vec<T> {
2175 fn push(&mut self, _:usize) {}
2176}
2177
2178fn foo(s: &mut S) {
2179 let $0items = &mut s.vec;
2180 items.push(0);
2181}"#,
2182 "Extract into variable",
2183 );
2184 }
2185
2186 #[test]
2187 fn dont_extract_const_mutable_reference_parameter() {
2188 check_assist_not_applicable_by_label(
2189 extract_variable,
2190 r#"
2191struct S {
2192 vec: Vec<u8>
2193}
2194
2195struct Vec<T>;
2196impl<T> Vec<T> {
2197 fn push(&mut self, _:usize) {}
2198}
2199
2200fn foo(s: &mut S) {
2201 $0s.vec$0.push(0);
2202}"#,
2203 "Extract into constant",
2204 );
2205 }
2206
2207 #[test]
2208 fn dont_extract_static_mutable_reference_parameter() {
2209 check_assist_not_applicable_by_label(
2210 extract_variable,
2211 r#"
2212struct S {
2213 vec: Vec<u8>
2214}
2215
2216struct Vec<T>;
2217impl<T> Vec<T> {
2218 fn push(&mut self, _:usize) {}
2219}
2220
2221fn foo(s: &mut S) {
2222 $0s.vec$0.push(0);
2223}"#,
2224 "Extract into static",
2225 );
2226 }
2227
2228 #[test]
2229 fn extract_var_mutable_reference_parameter_deep_nesting() {
2230 check_assist_by_label(
2231 extract_variable,
2232 r#"
2233struct Y {
2234 field: X
2235}
2236struct X {
2237 field: S
2238}
2239struct S {
2240 vec: Vec<u8>
2241}
2242struct Vec<T>;
2243impl<T> Vec<T> {
2244 fn push(&mut self, _:usize) {}
2245}
2246
2247fn foo(f: &mut Y) {
2248 $0f.field.field.vec$0.push(0);
2249}"#,
2250 r#"
2251struct Y {
2252 field: X
2253}
2254struct X {
2255 field: S
2256}
2257struct S {
2258 vec: Vec<u8>
2259}
2260struct Vec<T>;
2261impl<T> Vec<T> {
2262 fn push(&mut self, _:usize) {}
2263}
2264
2265fn foo(f: &mut Y) {
2266 let $0items = &mut f.field.field.vec;
2267 items.push(0);
2268}"#,
2269 "Extract into variable",
2270 );
2271 }
2272
2273 #[test]
2274 fn extract_var_reference_parameter() {
2275 check_assist_by_label(
2276 extract_variable,
2277 r#"
2278struct X;
2279
2280impl X {
2281 fn do_thing(&self) {
2282
2283 }
2284}
2285
2286struct S {
2287 sub: X
2288}
2289
2290fn foo(s: &S) {
2291 $0s.sub$0.do_thing();
2292}"#,
2293 r#"
2294struct X;
2295
2296impl X {
2297 fn do_thing(&self) {
2298
2299 }
2300}
2301
2302struct S {
2303 sub: X
2304}
2305
2306fn foo(s: &S) {
2307 let $0x = &s.sub;
2308 x.do_thing();
2309}"#,
2310 "Extract into variable",
2311 );
2312 }
2313
2314 #[test]
2315 fn extract_var_index_deref() {
2316 check_assist_by_label(
2317 extract_variable,
2318 r#"
2319//- minicore: index
2320struct X;
2321
2322impl core::ops::Index<usize> for X {
2323 type Output = i32;
2324 fn index(&self) -> &Self::Output { 0 }
2325}
2326
2327struct S {
2328 sub: X
2329}
2330
2331fn foo(s: &S) {
2332 $0s.sub$0[0];
2333}"#,
2334 r#"
2335struct X;
2336
2337impl core::ops::Index<usize> for X {
2338 type Output = i32;
2339 fn index(&self) -> &Self::Output { 0 }
2340}
2341
2342struct S {
2343 sub: X
2344}
2345
2346fn foo(s: &S) {
2347 let $0x = &s.sub;
2348 x[0];
2349}"#,
2350 "Extract into variable",
2351 );
2352 }
2353
2354 #[test]
2355 fn extract_var_reference_parameter_deep_nesting() {
2356 check_assist_by_label(
2357 extract_variable,
2358 r#"
2359struct Z;
2360impl Z {
2361 fn do_thing(&self) {
2362
2363 }
2364}
2365
2366struct Y {
2367 field: Z
2368}
2369
2370struct X {
2371 field: Y
2372}
2373
2374struct S {
2375 sub: X
2376}
2377
2378fn foo(s: &S) {
2379 $0s.sub.field.field$0.do_thing();
2380}"#,
2381 r#"
2382struct Z;
2383impl Z {
2384 fn do_thing(&self) {
2385
2386 }
2387}
2388
2389struct Y {
2390 field: Z
2391}
2392
2393struct X {
2394 field: Y
2395}
2396
2397struct S {
2398 sub: X
2399}
2400
2401fn foo(s: &S) {
2402 let $0z = &s.sub.field.field;
2403 z.do_thing();
2404}"#,
2405 "Extract into variable",
2406 );
2407 }
2408
2409 #[test]
2410 fn extract_var_regular_parameter() {
2411 check_assist_by_label(
2412 extract_variable,
2413 r#"
2414struct X;
2415
2416impl X {
2417 fn do_thing(&self) {
2418
2419 }
2420}
2421
2422struct S {
2423 sub: X
2424}
2425
2426fn foo(s: S) {
2427 $0s.sub$0.do_thing();
2428}"#,
2429 r#"
2430struct X;
2431
2432impl X {
2433 fn do_thing(&self) {
2434
2435 }
2436}
2437
2438struct S {
2439 sub: X
2440}
2441
2442fn foo(s: S) {
2443 let $0x = &s.sub;
2444 x.do_thing();
2445}"#,
2446 "Extract into variable",
2447 );
2448 }
2449
2450 #[test]
2451 fn extract_var_mutable_reference_local() {
2452 check_assist_by_label(
2453 extract_variable,
2454 r#"
2455struct X;
2456
2457struct S {
2458 sub: X
2459}
2460
2461impl S {
2462 fn new() -> S {
2463 S {
2464 sub: X::new()
2465 }
2466 }
2467}
2468
2469impl X {
2470 fn new() -> X {
2471 X { }
2472 }
2473 fn do_thing(&self) {
2474
2475 }
2476}
2477
2478
2479fn foo() {
2480 let local = &mut S::new();
2481 $0local.sub$0.do_thing();
2482}"#,
2483 r#"
2484struct X;
2485
2486struct S {
2487 sub: X
2488}
2489
2490impl S {
2491 fn new() -> S {
2492 S {
2493 sub: X::new()
2494 }
2495 }
2496}
2497
2498impl X {
2499 fn new() -> X {
2500 X { }
2501 }
2502 fn do_thing(&self) {
2503
2504 }
2505}
2506
2507
2508fn foo() {
2509 let local = &mut S::new();
2510 let $0x = &local.sub;
2511 x.do_thing();
2512}"#,
2513 "Extract into variable",
2514 );
2515 }
2516
2517 #[test]
2518 fn extract_var_reference_local() {
2519 check_assist_by_label(
2520 extract_variable,
2521 r#"
2522struct X;
2523
2524struct S {
2525 sub: X
2526}
2527
2528impl S {
2529 fn new() -> S {
2530 S {
2531 sub: X::new()
2532 }
2533 }
2534}
2535
2536impl X {
2537 fn new() -> X {
2538 X { }
2539 }
2540 fn do_thing(&self) {
2541
2542 }
2543}
2544
2545
2546fn foo() {
2547 let local = &S::new();
2548 $0local.sub$0.do_thing();
2549}"#,
2550 r#"
2551struct X;
2552
2553struct S {
2554 sub: X
2555}
2556
2557impl S {
2558 fn new() -> S {
2559 S {
2560 sub: X::new()
2561 }
2562 }
2563}
2564
2565impl X {
2566 fn new() -> X {
2567 X { }
2568 }
2569 fn do_thing(&self) {
2570
2571 }
2572}
2573
2574
2575fn foo() {
2576 let local = &S::new();
2577 let $0x = &local.sub;
2578 x.do_thing();
2579}"#,
2580 "Extract into variable",
2581 );
2582 }
2583
2584 #[test]
2585 fn extract_var_for_mutable_borrow() {
2586 check_assist_by_label(
2587 extract_variable,
2588 r#"
2589fn foo() {
2590 let v = &mut $00$0;
2591}"#,
2592 r#"
2593fn foo() {
2594 let mut $0var_name = 0;
2595 let v = &mut var_name;
2596}"#,
2597 "Extract into variable",
2598 );
2599 }
2600
2601 #[test]
2602 fn dont_extract_const_for_mutable_borrow() {
2603 check_assist_not_applicable_by_label(
2604 extract_variable,
2605 r#"
2606fn foo() {
2607 let v = &mut $00$0;
2608}"#,
2609 "Extract into constant",
2610 );
2611 }
2612
2613 #[test]
2614 fn dont_extract_static_for_mutable_borrow() {
2615 check_assist_not_applicable_by_label(
2616 extract_variable,
2617 r#"
2618fn foo() {
2619 let v = &mut $00$0;
2620}"#,
2621 "Extract into static",
2622 );
2623 }
2624
2625 #[test]
2626 fn generates_no_ref_on_calls() {
2627 check_assist_by_label(
2628 extract_variable,
2629 r#"
2630struct S;
2631impl S {
2632 fn do_work(&mut self) {}
2633}
2634fn bar() -> S { S }
2635fn foo() {
2636 $0bar()$0.do_work();
2637}"#,
2638 r#"
2639struct S;
2640impl S {
2641 fn do_work(&mut self) {}
2642}
2643fn bar() -> S { S }
2644fn foo() {
2645 let mut $0bar = bar();
2646 bar.do_work();
2647}"#,
2648 "Extract into variable",
2649 );
2650 }
2651
2652 #[test]
2653 fn generates_no_ref_for_deref() {
2654 check_assist_by_label(
2655 extract_variable,
2656 r#"
2657struct S;
2658impl S {
2659 fn do_work(&mut self) {}
2660}
2661fn bar() -> S { S }
2662fn foo() {
2663 let v = &mut &mut bar();
2664 $0(**v)$0.do_work();
2665}
2666"#,
2667 r#"
2668struct S;
2669impl S {
2670 fn do_work(&mut self) {}
2671}
2672fn bar() -> S { S }
2673fn foo() {
2674 let v = &mut &mut bar();
2675 let $0s = *v;
2676 s.do_work();
2677}
2678"#,
2679 "Extract into variable",
2680 );
2681 }
2682
2683 #[test]
2684 fn extract_string_literal() {
2685 check_assist_by_label(
2686 extract_variable,
2687 r#"
2688struct Entry<'a>(&'a str);
2689fn foo() {
2690 let entry = Entry($0"Hello"$0);
2691}
2692"#,
2693 r#"
2694struct Entry<'a>(&'a str);
2695fn foo() {
2696 let $0hello = "Hello";
2697 let entry = Entry(hello);
2698}
2699"#,
2700 "Extract into variable",
2701 );
2702
2703 check_assist_by_label(
2704 extract_variable,
2705 r#"
2706struct Entry<'a>(&'a str);
2707fn foo() {
2708 let entry = Entry($0"Hello"$0);
2709}
2710"#,
2711 r#"
2712struct Entry<'a>(&'a str);
2713fn foo() {
2714 const $0HELLO: &str = "Hello";
2715 let entry = Entry(HELLO);
2716}
2717"#,
2718 "Extract into constant",
2719 );
2720
2721 check_assist_by_label(
2722 extract_variable,
2723 r#"
2724struct Entry<'a>(&'a str);
2725fn foo() {
2726 let entry = Entry($0"Hello"$0);
2727}
2728"#,
2729 r#"
2730struct Entry<'a>(&'a str);
2731fn foo() {
2732 static $0HELLO: &str = "Hello";
2733 let entry = Entry(HELLO);
2734}
2735"#,
2736 "Extract into static",
2737 );
2738 }
2739
2740 #[test]
2741 fn extract_variable_string_literal_use_field_shorthand() {
2742 check_assist_by_label(
2745 extract_variable,
2746 r#"
2747struct Entry<'a> { message: &'a str }
2748fn foo() {
2749 let entry = Entry { message: $0"Hello"$0 };
2750}
2751"#,
2752 r#"
2753struct Entry<'a> { message: &'a str }
2754fn foo() {
2755 let $0message = "Hello";
2756 let entry = Entry { message };
2757}
2758"#,
2759 "Extract into variable",
2760 );
2761
2762 check_assist_by_label(
2763 extract_variable,
2764 r#"
2765struct Entry<'a> { message: &'a str }
2766fn foo() {
2767 let entry = Entry { message: $0"Hello"$0 };
2768}
2769"#,
2770 r#"
2771struct Entry<'a> { message: &'a str }
2772fn foo() {
2773 const $0HELLO: &str = "Hello";
2774 let entry = Entry { message: HELLO };
2775}
2776"#,
2777 "Extract into constant",
2778 );
2779
2780 check_assist_by_label(
2781 extract_variable,
2782 r#"
2783struct Entry<'a> { message: &'a str }
2784fn foo() {
2785 let entry = Entry { message: $0"Hello"$0 };
2786}
2787"#,
2788 r#"
2789struct Entry<'a> { message: &'a str }
2790fn foo() {
2791 static $0HELLO: &str = "Hello";
2792 let entry = Entry { message: HELLO };
2793}
2794"#,
2795 "Extract into static",
2796 );
2797 }
2798
2799 #[test]
2800 fn extract_variable_name_conflicts() {
2801 check_assist_by_label(
2802 extract_variable,
2803 r#"
2804struct S { x: i32 };
2805
2806fn main() {
2807 let s = 2;
2808 let t = $0S { x: 1 }$0;
2809 let t2 = t;
2810 let x = s;
2811}
2812"#,
2813 r#"
2814struct S { x: i32 };
2815
2816fn main() {
2817 let s = 2;
2818 let $0s1 = S { x: 1 };
2819 let t = s1;
2820 let t2 = t;
2821 let x = s;
2822}
2823"#,
2824 "Extract into variable",
2825 );
2826 }
2827
2828 #[test]
2829 fn extract_variable_in_token_tree() {
2830 check_assist_by_label(
2832 extract_variable,
2833 r#"
2834macro_rules! foo {
2835 (= $($t:tt)*) => {
2836 $($t)*
2837 };
2838}
2839
2840fn main() {
2841 let x = foo!(= $02 + 3$0 + 4);
2842}
2843"#,
2844 r#"
2845macro_rules! foo {
2846 (= $($t:tt)*) => {
2847 $($t)*
2848 };
2849}
2850
2851fn main() {
2852 let $0var_name = 2+3;
2853 let x = foo!(= var_name + 4);
2854}
2855"#,
2856 "Extract into variable",
2857 );
2858
2859 check_assist_by_label(
2860 extract_variable,
2861 r#"
2862macro_rules! foo {
2863 (= $($t:tt)*) => {
2864 $($t)*
2865 };
2866}
2867
2868fn main() {
2869 let x = foo!(= $02 +$0 3 + 4);
2870}
2871"#,
2872 r#"
2873macro_rules! foo {
2874 (= $($t:tt)*) => {
2875 $($t)*
2876 };
2877}
2878
2879fn main() {
2880 let $0var_name = 2+3;
2881 let x = foo!(= var_name + 4);
2882}
2883"#,
2884 "Extract into variable",
2885 );
2886
2887 check_assist_by_label(
2888 extract_variable,
2889 r#"
2890macro_rules! foo {
2891 (= $($t:tt)*) => {
2892 $($t)*
2893 };
2894}
2895
2896fn main() {
2897 let x = foo!(= $02 + 3 + 4$0);
2898}
2899"#,
2900 r#"
2901macro_rules! foo {
2902 (= $($t:tt)*) => {
2903 $($t)*
2904 };
2905}
2906
2907fn main() {
2908 let $0var_name = 2+3+4;
2909 let x = foo!(= var_name);
2910}
2911"#,
2912 "Extract into variable",
2913 );
2914
2915 check_assist_by_label(
2917 extract_variable,
2918 r#"
2919macro_rules! foo {
2920 (= $($t:tt)*) => {
2921 $($t)*
2922 };
2923}
2924
2925fn main() {
2926 let x = foo!(= {
2927 $02 + 3 + 4$0
2928 });
2929}
2930"#,
2931 r#"
2932macro_rules! foo {
2933 (= $($t:tt)*) => {
2934 $($t)*
2935 };
2936}
2937
2938fn main() {
2939 let $0var_name = 2+3+4;
2940 let x = foo!(= {
2941 var_name
2942 });
2943}
2944"#,
2945 "Extract into variable",
2946 );
2947 }
2948
2949 #[test]
2950 fn extract_variable_in_token_tree_record_expr() {
2951 check_assist_by_label(
2952 extract_variable,
2953 r#"
2954macro_rules! foo {
2955 (= $($t:tt)*) => {
2956 $($t)*
2957 };
2958}
2959
2960fn main() {
2961 let x = foo!(= Foo { x: $02 + 3$0 });
2962}
2963"#,
2964 r#"
2965macro_rules! foo {
2966 (= $($t:tt)*) => {
2967 $($t)*
2968 };
2969}
2970
2971fn main() {
2972 let $0x = 2+3;
2973 let x = foo!(= Foo { x: x });
2974}
2975"#,
2976 "Extract into variable",
2977 );
2978
2979 check_assist_by_label(
2980 extract_variable,
2981 r#"
2982macro_rules! foo {
2983 (= $($t:tt)*) => {
2984 $($t)*
2985 };
2986}
2987
2988fn main() {
2989 let x = foo!(= Foo { x: $02 + 3$0 + 4 });
2990}
2991"#,
2992 r#"
2993macro_rules! foo {
2994 (= $($t:tt)*) => {
2995 $($t)*
2996 };
2997}
2998
2999fn main() {
3000 let $0var_name = 2+3;
3001 let x = foo!(= Foo { x: var_name + 4 });
3002}
3003"#,
3004 "Extract into variable",
3005 );
3006 }
3007}