aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide_assists/src/utils/suggest_name.rs121
1 files changed, 114 insertions, 7 deletions
diff --git a/crates/ide_assists/src/utils/suggest_name.rs b/crates/ide_assists/src/utils/suggest_name.rs
index 345e9af40..d37c62642 100644
--- a/crates/ide_assists/src/utils/suggest_name.rs
+++ b/crates/ide_assists/src/utils/suggest_name.rs
@@ -29,6 +29,29 @@ const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
29/// `args.into_config()` -> `config` 29/// `args.into_config()` -> `config`
30/// `bytes.to_vec()` -> `vec` 30/// `bytes.to_vec()` -> `vec`
31const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"]; 31const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
32/// Useless methods that are stripped from expression
33///
34/// # Examples
35/// `var.name().to_string()` -> `var.name()`
36const USELESS_METHODS: &[&str] = &[
37 "to_string",
38 "as_str",
39 "to_owned",
40 "as_ref",
41 "clone",
42 "cloned",
43 "expect",
44 "expect_none",
45 "unwrap",
46 "unwrap_none",
47 "unwrap_or",
48 "unwrap_or_default",
49 "unwrap_or_else",
50 "unwrap_unchecked",
51 "iter",
52 "into_iter",
53 "iter_mut",
54];
32 55
33/// Suggest name of variable for given expression 56/// Suggest name of variable for given expression
34/// 57///
@@ -49,10 +72,39 @@ const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
49/// 72///
50/// Currently it sticks to the first name found. 73/// Currently it sticks to the first name found.
51pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String { 74pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String {
52 from_param(expr, sema) 75 // `from_param` does not benifit from stripping
53 .or_else(|| from_call(expr)) 76 // it need the largest context possible
54 .or_else(|| from_type(expr, sema)) 77 // so we check firstmost
55 .unwrap_or_else(|| "var_name".to_string()) 78 if let Some(name) = from_param(expr, sema) {
79 return name;
80 }
81
82 let mut next_expr = Some(expr.clone());
83 while let Some(expr) = next_expr {
84 let name = from_call(&expr).or_else(|| from_type(&expr, sema));
85 if let Some(name) = name {
86 return name;
87 }
88
89 match expr {
90 ast::Expr::RefExpr(inner) => next_expr = inner.expr(),
91 ast::Expr::BoxExpr(inner) => next_expr = inner.expr(),
92 ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(),
93 // ast::Expr::BlockExpr(block) => expr = block.tail_expr(),
94 ast::Expr::CastExpr(inner) => next_expr = inner.expr(),
95 ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => {
96 next_expr = method.receiver();
97 }
98 ast::Expr::ParenExpr(inner) => next_expr = inner.expr(),
99 ast::Expr::TryExpr(inner) => next_expr = inner.expr(),
100 ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::PrefixOp::Deref) => {
101 next_expr = prefix.expr()
102 }
103 _ => break,
104 }
105 }
106
107 "var_name".to_string()
56} 108}
57 109
58fn normalize(name: &str) -> Option<String> { 110fn normalize(name: &str) -> Option<String> {
@@ -76,6 +128,16 @@ fn is_valid_name(name: &str) -> bool {
76 } 128 }
77} 129}
78 130
131fn is_useless_method(method: &ast::MethodCallExpr) -> bool {
132 let ident = method.name_ref().and_then(|it| it.ident_token());
133
134 if let Some(ident) = ident {
135 USELESS_METHODS.contains(&ident.text())
136 } else {
137 false
138 }
139}
140
79fn from_call(expr: &ast::Expr) -> Option<String> { 141fn from_call(expr: &ast::Expr) -> Option<String> {
80 from_func_call(expr).or_else(|| from_method_call(expr)) 142 from_func_call(expr).or_else(|| from_method_call(expr))
81} 143}
@@ -99,15 +161,20 @@ fn from_method_call(expr: &ast::Expr) -> Option<String> {
99 _ => return None, 161 _ => return None,
100 }; 162 };
101 let ident = method.name_ref()?.ident_token()?; 163 let ident = method.name_ref()?.ident_token()?;
102 let name = normalize(ident.text())?; 164 let mut name = ident.text();
165
166 if USELESS_METHODS.contains(&name) {
167 return None;
168 }
103 169
104 for prefix in USELESS_METHOD_PREFIXES { 170 for prefix in USELESS_METHOD_PREFIXES {
105 if let Some(suffix) = name.strip_prefix(prefix) { 171 if let Some(suffix) = name.strip_prefix(prefix) {
106 return Some(suffix.to_string()); 172 name = suffix;
173 break;
107 } 174 }
108 } 175 }
109 176
110 Some(name) 177 normalize(&name)
111} 178}
112 179
113fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> { 180fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
@@ -767,4 +834,44 @@ mod tests {
767 ); 834 );
768 } 835 }
769 } 836 }
837
838 mod variable {
839 use super::*;
840
841 #[test]
842 fn ref_call() {
843 check_name_suggestion(
844 |e, c| Some(variable(e, c)),
845 r#"
846 fn foo() {
847 $0&bar(1, 3)$0
848 }"#,
849 "bar",
850 );
851 }
852
853 #[test]
854 fn name_to_string() {
855 check_name_suggestion(
856 |e, c| Some(variable(e, c)),
857 r#"
858 fn foo() {
859 $0function.name().to_string()$0
860 }"#,
861 "name",
862 );
863 }
864
865 #[test]
866 fn nested_useless_method() {
867 check_name_suggestion(
868 |e, c| Some(variable(e, c)),
869 r#"
870 fn foo() {
871 $0function.name().as_ref().unwrap().to_string()$0
872 }"#,
873 "name",
874 );
875 }
876 }
770} 877}