aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/handlers/extract_variable.rs294
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 @@
1use stdx::format_to; 1use itertools::Itertools;
2use stdx::{format_to, to_lower_snake_case};
2use syntax::{ 3use 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
177fn 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
186fn 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
197fn 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
210fn 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
219fn 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
242fn 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
249fn 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)]
177mod tests { 261mod tests {
178 use test_utils::mark; 262 use test_utils::mark;
@@ -274,8 +358,8 @@ fn foo() {
274"#, 358"#,
275 r#" 359 r#"
276fn foo() { 360fn 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 "
403fn main() { 487fn 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#"
648struct Test(i32);
649
650fn foo() -> Test {
651 $0{ Test(10) }$0
652}
653"#,
654 r#"
655struct Test(i32);
656
657fn 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#"
670fn bar(test: u32, size: u32)
671
672fn foo() {
673 bar(1, $01+1$0);
674}
675"#,
676 r#"
677fn bar(test: u32, size: u32)
678
679fn 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#"
692struct TextSize(u32);
693fn bar(test: u32, size: TextSize)
694
695fn foo() {
696 bar(1, $0{ TextSize(1+1) }$0);
697}
698"#,
699 r#"
700struct TextSize(u32);
701fn bar(test: u32, size: TextSize)
702
703fn 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#"
716fn is_required(test: u32, size: u32) -> bool
717
718fn foo() -> bool {
719 $0is_required(1, 2)$0
720}
721"#,
722 r#"
723fn is_required(test: u32, size: u32) -> bool
724
725fn 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#"
738struct S;
739impl S {
740 fn bar(&self, n: u32) -> u32 { n }
741}
742
743fn foo() -> u32 {
744 $0S.bar(1)$0
745}
746"#,
747 r#"
748struct S;
749impl S {
750 fn bar(&self, n: u32) -> u32 { n }
751}
752
753fn 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#"
766struct S;
767impl S {
768 fn bar(&self, n: u32, size: u32) { n }
769}
770
771fn foo() {
772 S.bar($01 + 1$0, 2)
773}
774"#,
775 r#"
776struct S;
777impl S {
778 fn bar(&self, n: u32, size: u32) { n }
779}
780
781fn 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#"
794struct S;
795impl S {
796 fn bar(&self, n: u32, size: u32) { n }
797}
798
799fn foo() {
800 S::bar(&S, $01 + 1$0, 2)
801}
802"#,
803 r#"
804struct S;
805impl S {
806 fn bar(&self, n: u32, size: u32) { n }
807}
808
809fn 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#"
822fn bar(test: u32, size: u32)
823
824fn foo() {
825 bar(1, $0symbol_size(1, 2)$0);
826}
827"#,
828 r#"
829fn bar(test: u32, size: u32)
830
831fn 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 }