Skip to content

Commit 3b75dda

Browse files
cpud36matklad
authored andcommitted
try to suggest name when extracting variable
1 parent 8eee914 commit 3b75dda

File tree

1 file changed

+287
-7
lines changed

1 file changed

+287
-7
lines changed

crates/ide_assists/src/handlers/extract_variable.rs

Lines changed: 287 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use stdx::format_to;
1+
use itertools::Itertools;
2+
use stdx::{format_to, to_lower_snake_case};
23
use syntax::{
3-
ast::{self, AstNode},
4+
ast::{self, AstNode, NameOwner},
45
SyntaxKind::{
56
BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
67
},
@@ -54,7 +55,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
5455

5556
let var_name = match &field_shorthand {
5657
Some(it) => it.to_string(),
57-
None => "var_name".to_string(),
58+
None => suggest_variable_name(ctx, &to_extract),
5859
};
5960
let expr_range = match &field_shorthand {
6061
Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
@@ -173,6 +174,89 @@ impl Anchor {
173174
}
174175
}
175176

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+
176260
#[cfg(test)]
177261
mod tests {
178262
use test_utils::mark;
@@ -274,8 +358,8 @@ fn foo() {
274358
"#,
275359
r#"
276360
fn foo() {
277-
let $0var_name = bar(1 + 1);
278-
var_name
361+
let $0bar = bar(1 + 1);
362+
bar
279363
}
280364
"#,
281365
)
@@ -401,8 +485,8 @@ fn main() {
401485
",
402486
"
403487
fn main() {
404-
let $0var_name = bar.foo();
405-
let v = var_name;
488+
let $0foo = bar.foo();
489+
let v = foo;
406490
}
407491
",
408492
);
@@ -556,6 +640,202 @@ fn main() {
556640
)
557641
}
558642

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+
559839
#[test]
560840
fn test_extract_var_for_return_not_applicable() {
561841
check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");

0 commit comments

Comments
 (0)