From 3b75dda7454ca26afc70aae55d48706441eceff9 Mon Sep 17 00:00:00 2001 From: Vladyslav Katasonov Date: Tue, 16 Feb 2021 04:43:32 +0300 Subject: try to suggest name when extracting variable --- .../ide_assists/src/handlers/extract_variable.rs | 294 ++++++++++++++++++++- 1 file changed, 287 insertions(+), 7 deletions(-) (limited to 'crates/ide_assists') 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 @@ -use stdx::format_to; +use itertools::Itertools; +use stdx::{format_to, to_lower_snake_case}; use syntax::{ - ast::{self, AstNode}, + ast::{self, AstNode, NameOwner}, SyntaxKind::{ BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR, }, @@ -54,7 +55,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option let var_name = match &field_shorthand { Some(it) => it.to_string(), - None => "var_name".to_string(), + None => suggest_variable_name(ctx, &to_extract), }; let expr_range = match &field_shorthand { Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()), @@ -173,6 +174,89 @@ impl Anchor { } } +fn suggest_variable_name(ctx: &AssistContext, expr: &ast::Expr) -> String { + // FIXME: account for existing names in the scope + suggest_name_from_func(expr) + .or_else(|| suggest_name_from_method(expr)) + .or_else(|| suggest_name_from_param(ctx, expr)) + .or_else(|| suggest_name_by_type(ctx, expr)) + .unwrap_or_else(|| "var_name".to_string()) +} + +fn normalize_name(name: &str) -> Option { + let name = to_lower_snake_case(name); + + let useless_names = ["new", "default", "some", "none", "ok", "err"]; + if useless_names.contains(&name.as_str()) { + return None; + } + + Some(name) +} + +fn suggest_name_from_func(expr: &ast::Expr) -> Option { + let call = match expr { + ast::Expr::CallExpr(call) => call, + _ => return None, + }; + let func = match call.expr()? { + ast::Expr::PathExpr(path) => path, + _ => return None, + }; + let ident = func.path()?.segment()?.name_ref()?.ident_token()?; + normalize_name(ident.text()) +} + +fn suggest_name_from_method(expr: &ast::Expr) -> Option { + let method = match expr { + ast::Expr::MethodCallExpr(call) => call, + _ => return None, + }; + let ident = method.name_ref()?.ident_token()?; + normalize_name(ident.text()) +} + +fn suggest_name_from_param(ctx: &AssistContext, expr: &ast::Expr) -> Option { + let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?; + let args_parent = arg_list.syntax().parent()?; + let func = if let Some(call) = ast::CallExpr::cast(args_parent.clone()) { + let func = call.expr()?; + let func_ty = ctx.sema.type_of_expr(&func)?; + func_ty.as_callable(ctx.db())? + } else if let Some(method) = ast::MethodCallExpr::cast(args_parent) { + ctx.sema.resolve_method_call_as_callable(&method)? + } else { + return None; + }; + + let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap(); + let (pat, _) = func.params(ctx.db()).into_iter().nth(idx)?; + let param = match pat? { + either::Either::Right(ast::Pat::IdentPat(param)) => param, + _ => return None, + }; + let name = param.name()?; + normalize_name(&name.to_string()) +} + +fn suggest_name_by_type(ctx: &AssistContext, expr: &ast::Expr) -> Option { + let ty = ctx.sema.type_of_expr(expr)?; + let ty = ty.remove_ref().unwrap_or(ty); + + name_from_type(ty, ctx) +} + +fn name_from_type(ty: hir::Type, ctx: &AssistContext) -> Option { + let name = if let Some(adt) = ty.as_adt() { + adt.name(ctx.db()).to_string() + } else if let Some(trait_) = ty.as_dyn_trait() { + trait_.name(ctx.db()).to_string() + } else { + return None; + }; + normalize_name(&name) +} + #[cfg(test)] mod tests { use test_utils::mark; @@ -274,8 +358,8 @@ fn foo() { "#, r#" fn foo() { - let $0var_name = bar(1 + 1); - var_name + let $0bar = bar(1 + 1); + bar } "#, ) @@ -401,8 +485,8 @@ fn main() { ", " fn main() { - let $0var_name = bar.foo(); - let v = var_name; + let $0foo = bar.foo(); + let v = foo; } ", ); @@ -556,6 +640,202 @@ fn main() { ) } + #[test] + fn extract_var_name_from_type() { + check_assist( + extract_variable, + r#" +struct Test(i32); + +fn foo() -> Test { + $0{ Test(10) }$0 +} +"#, + r#" +struct Test(i32); + +fn foo() -> Test { + let $0test = { Test(10) }; + test +} +"#, + ) + } + + #[test] + fn extract_var_name_from_parameter() { + check_assist( + extract_variable, + r#" +fn bar(test: u32, size: u32) + +fn foo() { + bar(1, $01+1$0); +} +"#, + r#" +fn bar(test: u32, size: u32) + +fn foo() { + let $0size = 1+1; + bar(1, size); +} +"#, + ) + } + + #[test] + fn extract_var_parameter_name_has_precedence_over_type() { + check_assist( + extract_variable, + r#" +struct TextSize(u32); +fn bar(test: u32, size: TextSize) + +fn foo() { + bar(1, $0{ TextSize(1+1) }$0); +} +"#, + r#" +struct TextSize(u32); +fn bar(test: u32, size: TextSize) + +fn foo() { + let $0size = { TextSize(1+1) }; + bar(1, size); +} +"#, + ) + } + + #[test] + fn extract_var_name_from_function() { + check_assist( + extract_variable, + r#" +fn is_required(test: u32, size: u32) -> bool + +fn foo() -> bool { + $0is_required(1, 2)$0 +} +"#, + r#" +fn is_required(test: u32, size: u32) -> bool + +fn foo() -> bool { + let $0is_required = is_required(1, 2); + is_required +} +"#, + ) + } + + #[test] + fn extract_var_name_from_method() { + check_assist( + extract_variable, + r#" +struct S; +impl S { + fn bar(&self, n: u32) -> u32 { n } +} + +fn foo() -> u32 { + $0S.bar(1)$0 +} +"#, + r#" +struct S; +impl S { + fn bar(&self, n: u32) -> u32 { n } +} + +fn foo() -> u32 { + let $0bar = S.bar(1); + bar +} +"#, + ) + } + + #[test] + fn extract_var_name_from_method_param() { + check_assist( + extract_variable, + r#" +struct S; +impl S { + fn bar(&self, n: u32, size: u32) { n } +} + +fn foo() { + S.bar($01 + 1$0, 2) +} +"#, + r#" +struct S; +impl S { + fn bar(&self, n: u32, size: u32) { n } +} + +fn foo() { + let $0n = 1 + 1; + S.bar(n, 2) +} +"#, + ) + } + + #[test] + fn extract_var_name_from_ufcs_method_param() { + check_assist( + extract_variable, + r#" +struct S; +impl S { + fn bar(&self, n: u32, size: u32) { n } +} + +fn foo() { + S::bar(&S, $01 + 1$0, 2) +} +"#, + r#" +struct S; +impl S { + fn bar(&self, n: u32, size: u32) { n } +} + +fn foo() { + let $0n = 1 + 1; + S::bar(&S, n, 2) +} +"#, + ) + } + + #[test] + fn extract_var_function_name_has_precedence() { + check_assist( + extract_variable, + r#" +fn bar(test: u32, size: u32) + +fn foo() { + bar(1, $0symbol_size(1, 2)$0); +} +"#, + r#" +fn bar(test: u32, size: u32) + +fn foo() { + let $0symbol_size = symbol_size(1, 2); + bar(1, symbol_size); +} +"#, + ) + } + #[test] fn test_extract_var_for_return_not_applicable() { check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } "); -- cgit v1.2.3