|
1 |
| -use stdx::format_to; |
| 1 | +use itertools::Itertools; |
| 2 | +use stdx::{format_to, to_lower_snake_case}; |
2 | 3 | use syntax::{
|
3 |
| - ast::{self, AstNode}, |
| 4 | + ast::{self, AstNode, NameOwner}, |
4 | 5 | SyntaxKind::{
|
5 | 6 | BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
|
6 | 7 | },
|
@@ -54,7 +55,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
|
54 | 55 |
|
55 | 56 | let var_name = match &field_shorthand {
|
56 | 57 | Some(it) => it.to_string(),
|
57 |
| - None => "var_name".to_string(), |
| 58 | + None => suggest_variable_name(ctx, &to_extract), |
58 | 59 | };
|
59 | 60 | let expr_range = match &field_shorthand {
|
60 | 61 | Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
|
@@ -173,6 +174,89 @@ impl Anchor {
|
173 | 174 | }
|
174 | 175 | }
|
175 | 176 |
|
| 177 | +fn suggest_variable_name(ctx: &AssistContext, expr: &ast::Expr) -> String { |
| 178 | + // FIXME: account for existing names in the scope |
| 179 | + suggest_name_from_func(expr) |
| 180 | + .or_else(|| suggest_name_from_method(expr)) |
| 181 | + .or_else(|| suggest_name_from_param(ctx, expr)) |
| 182 | + .or_else(|| suggest_name_by_type(ctx, expr)) |
| 183 | + .unwrap_or_else(|| "var_name".to_string()) |
| 184 | +} |
| 185 | + |
| 186 | +fn normalize_name(name: &str) -> Option<String> { |
| 187 | + let name = to_lower_snake_case(name); |
| 188 | + |
| 189 | + let useless_names = ["new", "default", "some", "none", "ok", "err"]; |
| 190 | + if useless_names.contains(&name.as_str()) { |
| 191 | + return None; |
| 192 | + } |
| 193 | + |
| 194 | + Some(name) |
| 195 | +} |
| 196 | + |
| 197 | +fn suggest_name_from_func(expr: &ast::Expr) -> Option<String> { |
| 198 | + let call = match expr { |
| 199 | + ast::Expr::CallExpr(call) => call, |
| 200 | + _ => return None, |
| 201 | + }; |
| 202 | + let func = match call.expr()? { |
| 203 | + ast::Expr::PathExpr(path) => path, |
| 204 | + _ => return None, |
| 205 | + }; |
| 206 | + let ident = func.path()?.segment()?.name_ref()?.ident_token()?; |
| 207 | + normalize_name(ident.text()) |
| 208 | +} |
| 209 | + |
| 210 | +fn suggest_name_from_method(expr: &ast::Expr) -> Option<String> { |
| 211 | + let method = match expr { |
| 212 | + ast::Expr::MethodCallExpr(call) => call, |
| 213 | + _ => return None, |
| 214 | + }; |
| 215 | + let ident = method.name_ref()?.ident_token()?; |
| 216 | + normalize_name(ident.text()) |
| 217 | +} |
| 218 | + |
| 219 | +fn suggest_name_from_param(ctx: &AssistContext, expr: &ast::Expr) -> Option<String> { |
| 220 | + let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?; |
| 221 | + let args_parent = arg_list.syntax().parent()?; |
| 222 | + let func = if let Some(call) = ast::CallExpr::cast(args_parent.clone()) { |
| 223 | + let func = call.expr()?; |
| 224 | + let func_ty = ctx.sema.type_of_expr(&func)?; |
| 225 | + func_ty.as_callable(ctx.db())? |
| 226 | + } else if let Some(method) = ast::MethodCallExpr::cast(args_parent) { |
| 227 | + ctx.sema.resolve_method_call_as_callable(&method)? |
| 228 | + } else { |
| 229 | + return None; |
| 230 | + }; |
| 231 | + |
| 232 | + let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap(); |
| 233 | + let (pat, _) = func.params(ctx.db()).into_iter().nth(idx)?; |
| 234 | + let param = match pat? { |
| 235 | + either::Either::Right(ast::Pat::IdentPat(param)) => param, |
| 236 | + _ => return None, |
| 237 | + }; |
| 238 | + let name = param.name()?; |
| 239 | + normalize_name(&name.to_string()) |
| 240 | +} |
| 241 | + |
| 242 | +fn suggest_name_by_type(ctx: &AssistContext, expr: &ast::Expr) -> Option<String> { |
| 243 | + let ty = ctx.sema.type_of_expr(expr)?; |
| 244 | + let ty = ty.remove_ref().unwrap_or(ty); |
| 245 | + |
| 246 | + name_from_type(ty, ctx) |
| 247 | +} |
| 248 | + |
| 249 | +fn name_from_type(ty: hir::Type, ctx: &AssistContext) -> Option<String> { |
| 250 | + let name = if let Some(adt) = ty.as_adt() { |
| 251 | + adt.name(ctx.db()).to_string() |
| 252 | + } else if let Some(trait_) = ty.as_dyn_trait() { |
| 253 | + trait_.name(ctx.db()).to_string() |
| 254 | + } else { |
| 255 | + return None; |
| 256 | + }; |
| 257 | + normalize_name(&name) |
| 258 | +} |
| 259 | + |
176 | 260 | #[cfg(test)]
|
177 | 261 | mod tests {
|
178 | 262 | use test_utils::mark;
|
@@ -274,8 +358,8 @@ fn foo() {
|
274 | 358 | "#,
|
275 | 359 | r#"
|
276 | 360 | fn foo() {
|
277 |
| - let $0var_name = bar(1 + 1); |
278 |
| - var_name |
| 361 | + let $0bar = bar(1 + 1); |
| 362 | + bar |
279 | 363 | }
|
280 | 364 | "#,
|
281 | 365 | )
|
@@ -401,8 +485,8 @@ fn main() {
|
401 | 485 | ",
|
402 | 486 | "
|
403 | 487 | fn main() {
|
404 |
| - let $0var_name = bar.foo(); |
405 |
| - let v = var_name; |
| 488 | + let $0foo = bar.foo(); |
| 489 | + let v = foo; |
406 | 490 | }
|
407 | 491 | ",
|
408 | 492 | );
|
@@ -556,6 +640,202 @@ fn main() {
|
556 | 640 | )
|
557 | 641 | }
|
558 | 642 |
|
| 643 | + #[test] |
| 644 | + fn extract_var_name_from_type() { |
| 645 | + check_assist( |
| 646 | + extract_variable, |
| 647 | + r#" |
| 648 | +struct Test(i32); |
| 649 | +
|
| 650 | +fn foo() -> Test { |
| 651 | + $0{ Test(10) }$0 |
| 652 | +} |
| 653 | +"#, |
| 654 | + r#" |
| 655 | +struct Test(i32); |
| 656 | +
|
| 657 | +fn foo() -> Test { |
| 658 | + let $0test = { Test(10) }; |
| 659 | + test |
| 660 | +} |
| 661 | +"#, |
| 662 | + ) |
| 663 | + } |
| 664 | + |
| 665 | + #[test] |
| 666 | + fn extract_var_name_from_parameter() { |
| 667 | + check_assist( |
| 668 | + extract_variable, |
| 669 | + r#" |
| 670 | +fn bar(test: u32, size: u32) |
| 671 | +
|
| 672 | +fn foo() { |
| 673 | + bar(1, $01+1$0); |
| 674 | +} |
| 675 | +"#, |
| 676 | + r#" |
| 677 | +fn bar(test: u32, size: u32) |
| 678 | +
|
| 679 | +fn foo() { |
| 680 | + let $0size = 1+1; |
| 681 | + bar(1, size); |
| 682 | +} |
| 683 | +"#, |
| 684 | + ) |
| 685 | + } |
| 686 | + |
| 687 | + #[test] |
| 688 | + fn extract_var_parameter_name_has_precedence_over_type() { |
| 689 | + check_assist( |
| 690 | + extract_variable, |
| 691 | + r#" |
| 692 | +struct TextSize(u32); |
| 693 | +fn bar(test: u32, size: TextSize) |
| 694 | +
|
| 695 | +fn foo() { |
| 696 | + bar(1, $0{ TextSize(1+1) }$0); |
| 697 | +} |
| 698 | +"#, |
| 699 | + r#" |
| 700 | +struct TextSize(u32); |
| 701 | +fn bar(test: u32, size: TextSize) |
| 702 | +
|
| 703 | +fn foo() { |
| 704 | + let $0size = { TextSize(1+1) }; |
| 705 | + bar(1, size); |
| 706 | +} |
| 707 | +"#, |
| 708 | + ) |
| 709 | + } |
| 710 | + |
| 711 | + #[test] |
| 712 | + fn extract_var_name_from_function() { |
| 713 | + check_assist( |
| 714 | + extract_variable, |
| 715 | + r#" |
| 716 | +fn is_required(test: u32, size: u32) -> bool |
| 717 | +
|
| 718 | +fn foo() -> bool { |
| 719 | + $0is_required(1, 2)$0 |
| 720 | +} |
| 721 | +"#, |
| 722 | + r#" |
| 723 | +fn is_required(test: u32, size: u32) -> bool |
| 724 | +
|
| 725 | +fn foo() -> bool { |
| 726 | + let $0is_required = is_required(1, 2); |
| 727 | + is_required |
| 728 | +} |
| 729 | +"#, |
| 730 | + ) |
| 731 | + } |
| 732 | + |
| 733 | + #[test] |
| 734 | + fn extract_var_name_from_method() { |
| 735 | + check_assist( |
| 736 | + extract_variable, |
| 737 | + r#" |
| 738 | +struct S; |
| 739 | +impl S { |
| 740 | + fn bar(&self, n: u32) -> u32 { n } |
| 741 | +} |
| 742 | +
|
| 743 | +fn foo() -> u32 { |
| 744 | + $0S.bar(1)$0 |
| 745 | +} |
| 746 | +"#, |
| 747 | + r#" |
| 748 | +struct S; |
| 749 | +impl S { |
| 750 | + fn bar(&self, n: u32) -> u32 { n } |
| 751 | +} |
| 752 | +
|
| 753 | +fn foo() -> u32 { |
| 754 | + let $0bar = S.bar(1); |
| 755 | + bar |
| 756 | +} |
| 757 | +"#, |
| 758 | + ) |
| 759 | + } |
| 760 | + |
| 761 | + #[test] |
| 762 | + fn extract_var_name_from_method_param() { |
| 763 | + check_assist( |
| 764 | + extract_variable, |
| 765 | + r#" |
| 766 | +struct S; |
| 767 | +impl S { |
| 768 | + fn bar(&self, n: u32, size: u32) { n } |
| 769 | +} |
| 770 | +
|
| 771 | +fn foo() { |
| 772 | + S.bar($01 + 1$0, 2) |
| 773 | +} |
| 774 | +"#, |
| 775 | + r#" |
| 776 | +struct S; |
| 777 | +impl S { |
| 778 | + fn bar(&self, n: u32, size: u32) { n } |
| 779 | +} |
| 780 | +
|
| 781 | +fn foo() { |
| 782 | + let $0n = 1 + 1; |
| 783 | + S.bar(n, 2) |
| 784 | +} |
| 785 | +"#, |
| 786 | + ) |
| 787 | + } |
| 788 | + |
| 789 | + #[test] |
| 790 | + fn extract_var_name_from_ufcs_method_param() { |
| 791 | + check_assist( |
| 792 | + extract_variable, |
| 793 | + r#" |
| 794 | +struct S; |
| 795 | +impl S { |
| 796 | + fn bar(&self, n: u32, size: u32) { n } |
| 797 | +} |
| 798 | +
|
| 799 | +fn foo() { |
| 800 | + S::bar(&S, $01 + 1$0, 2) |
| 801 | +} |
| 802 | +"#, |
| 803 | + r#" |
| 804 | +struct S; |
| 805 | +impl S { |
| 806 | + fn bar(&self, n: u32, size: u32) { n } |
| 807 | +} |
| 808 | +
|
| 809 | +fn foo() { |
| 810 | + let $0n = 1 + 1; |
| 811 | + S::bar(&S, n, 2) |
| 812 | +} |
| 813 | +"#, |
| 814 | + ) |
| 815 | + } |
| 816 | + |
| 817 | + #[test] |
| 818 | + fn extract_var_function_name_has_precedence() { |
| 819 | + check_assist( |
| 820 | + extract_variable, |
| 821 | + r#" |
| 822 | +fn bar(test: u32, size: u32) |
| 823 | +
|
| 824 | +fn foo() { |
| 825 | + bar(1, $0symbol_size(1, 2)$0); |
| 826 | +} |
| 827 | +"#, |
| 828 | + r#" |
| 829 | +fn bar(test: u32, size: u32) |
| 830 | +
|
| 831 | +fn foo() { |
| 832 | + let $0symbol_size = symbol_size(1, 2); |
| 833 | + bar(1, symbol_size); |
| 834 | +} |
| 835 | +"#, |
| 836 | + ) |
| 837 | + } |
| 838 | + |
559 | 839 | #[test]
|
560 | 840 | fn test_extract_var_for_return_not_applicable() {
|
561 | 841 | check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");
|
|
0 commit comments