strip useless methods, and unary ops in suggest_name

This commit is contained in:
Vladyslav Katasonov 2021-02-17 00:42:58 +03:00 committed by Aleksey Kladov
parent afc68277f6
commit 7066e6b362

View File

@ -29,6 +29,29 @@ const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
/// `args.into_config()` -> `config`
/// `bytes.to_vec()` -> `vec`
const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
/// Useless methods that are stripped from expression
///
/// # Examples
/// `var.name().to_string()` -> `var.name()`
const USELESS_METHODS: &[&str] = &[
"to_string",
"as_str",
"to_owned",
"as_ref",
"clone",
"cloned",
"expect",
"expect_none",
"unwrap",
"unwrap_none",
"unwrap_or",
"unwrap_or_default",
"unwrap_or_else",
"unwrap_unchecked",
"iter",
"into_iter",
"iter_mut",
];
/// Suggest name of variable for given expression
///
@ -49,10 +72,39 @@ const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
///
/// Currently it sticks to the first name found.
pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String {
from_param(expr, sema)
.or_else(|| from_call(expr))
.or_else(|| from_type(expr, sema))
.unwrap_or_else(|| "var_name".to_string())
// `from_param` does not benifit from stripping
// it need the largest context possible
// so we check firstmost
if let Some(name) = from_param(expr, sema) {
return name;
}
let mut next_expr = Some(expr.clone());
while let Some(expr) = next_expr {
let name = from_call(&expr).or_else(|| from_type(&expr, sema));
if let Some(name) = name {
return name;
}
match expr {
ast::Expr::RefExpr(inner) => next_expr = inner.expr(),
ast::Expr::BoxExpr(inner) => next_expr = inner.expr(),
ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(),
// ast::Expr::BlockExpr(block) => expr = block.tail_expr(),
ast::Expr::CastExpr(inner) => next_expr = inner.expr(),
ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => {
next_expr = method.receiver();
}
ast::Expr::ParenExpr(inner) => next_expr = inner.expr(),
ast::Expr::TryExpr(inner) => next_expr = inner.expr(),
ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::PrefixOp::Deref) => {
next_expr = prefix.expr()
}
_ => break,
}
}
"var_name".to_string()
}
fn normalize(name: &str) -> Option<String> {
@ -76,6 +128,16 @@ fn is_valid_name(name: &str) -> bool {
}
}
fn is_useless_method(method: &ast::MethodCallExpr) -> bool {
let ident = method.name_ref().and_then(|it| it.ident_token());
if let Some(ident) = ident {
USELESS_METHODS.contains(&ident.text())
} else {
false
}
}
fn from_call(expr: &ast::Expr) -> Option<String> {
from_func_call(expr).or_else(|| from_method_call(expr))
}
@ -99,15 +161,20 @@ fn from_method_call(expr: &ast::Expr) -> Option<String> {
_ => return None,
};
let ident = method.name_ref()?.ident_token()?;
let name = normalize(ident.text())?;
let mut name = ident.text();
if USELESS_METHODS.contains(&name) {
return None;
}
for prefix in USELESS_METHOD_PREFIXES {
if let Some(suffix) = name.strip_prefix(prefix) {
return Some(suffix.to_string());
name = suffix;
break;
}
}
Some(name)
normalize(&name)
}
fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
@ -767,4 +834,44 @@ mod tests {
);
}
}
mod variable {
use super::*;
#[test]
fn ref_call() {
check_name_suggestion(
|e, c| Some(variable(e, c)),
r#"
fn foo() {
$0&bar(1, 3)$0
}"#,
"bar",
);
}
#[test]
fn name_to_string() {
check_name_suggestion(
|e, c| Some(variable(e, c)),
r#"
fn foo() {
$0function.name().to_string()$0
}"#,
"name",
);
}
#[test]
fn nested_useless_method() {
check_name_suggestion(
|e, c| Some(variable(e, c)),
r#"
fn foo() {
$0function.name().as_ref().unwrap().to_string()$0
}"#,
"name",
);
}
}
}