diff options
author | Vladyslav Katasonov <[email protected]> | 2021-02-16 01:43:32 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2021-03-02 13:23:00 +0000 |
commit | 3b75dda7454ca26afc70aae55d48706441eceff9 (patch) | |
tree | 6fa1f586b671b5c8c8ff0677fddb8555ea363469 /crates/ide_assists | |
parent | 8eee9149e87ea58d4191d04ebe6faf57ac8485a3 (diff) |
try to suggest name when extracting variable
Diffstat (limited to 'crates/ide_assists')
-rw-r--r-- | crates/ide_assists/src/handlers/extract_variable.rs | 294 |
1 files changed, 287 insertions, 7 deletions
diff --git a/crates/ide_assists/src/handlers/extract_variable.rs b/crates/ide_assists/src/handlers/extract_variable.rs index 98f3dc6ca..32e54fd57 100644 --- a/crates/ide_assists/src/handlers/extract_variable.rs +++ b/crates/ide_assists/src/handlers/extract_variable.rs | |||
@@ -1,6 +1,7 @@ | |||
1 | use stdx::format_to; | 1 | use itertools::Itertools; |
2 | use stdx::{format_to, to_lower_snake_case}; | ||
2 | use syntax::{ | 3 | use syntax::{ |
3 | ast::{self, AstNode}, | 4 | ast::{self, AstNode, NameOwner}, |
4 | SyntaxKind::{ | 5 | SyntaxKind::{ |
5 | BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, | 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 | let var_name = match &field_shorthand { | 56 | let var_name = match &field_shorthand { |
56 | Some(it) => it.to_string(), | 57 | Some(it) => it.to_string(), |
57 | None => "var_name".to_string(), | 58 | None => suggest_variable_name(ctx, &to_extract), |
58 | }; | 59 | }; |
59 | let expr_range = match &field_shorthand { | 60 | let expr_range = match &field_shorthand { |
60 | Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), | 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 | #[cfg(test)] | 260 | #[cfg(test)] |
177 | mod tests { | 261 | mod tests { |
178 | use test_utils::mark; | 262 | use test_utils::mark; |
@@ -274,8 +358,8 @@ fn foo() { | |||
274 | "#, | 358 | "#, |
275 | r#" | 359 | r#" |
276 | fn foo() { | 360 | fn foo() { |
277 | let $0var_name = bar(1 + 1); | 361 | let $0bar = bar(1 + 1); |
278 | var_name | 362 | bar |
279 | } | 363 | } |
280 | "#, | 364 | "#, |
281 | ) | 365 | ) |
@@ -401,8 +485,8 @@ fn main() { | |||
401 | ", | 485 | ", |
402 | " | 486 | " |
403 | fn main() { | 487 | fn main() { |
404 | let $0var_name = bar.foo(); | 488 | let $0foo = bar.foo(); |
405 | let v = var_name; | 489 | let v = foo; |
406 | } | 490 | } |
407 | ", | 491 | ", |
408 | ); | 492 | ); |
@@ -557,6 +641,202 @@ fn main() { | |||
557 | } | 641 | } |
558 | 642 | ||
559 | #[test] | 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 | |||
839 | #[test] | ||
560 | fn test_extract_var_for_return_not_applicable() { | 840 | fn test_extract_var_for_return_not_applicable() { |
561 | check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); | 841 | check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); |
562 | } | 842 | } |