From 620af0ef2f1e7ba6d39bca8ee95cb5e458b4e09b Mon Sep 17 00:00:00 2001 From: Giga Bowser <45986823+Giga-Bowser@users.noreply.github.com> Date: Sat, 14 Dec 2024 13:36:11 -0500 Subject: [PATCH] feat: Use string literal contents as a name when extracting into variable --- .../src/handlers/extract_variable.rs | 183 ++++++++++++++++-- 1 file changed, 168 insertions(+), 15 deletions(-) diff --git a/crates/ide-assists/src/handlers/extract_variable.rs b/crates/ide-assists/src/handlers/extract_variable.rs index a8d71ed7f4..6735d7dcbe 100644 --- a/crates/ide-assists/src/handlers/extract_variable.rs +++ b/crates/ide-assists/src/handlers/extract_variable.rs @@ -1,5 +1,8 @@ use hir::{HirDisplay, TypeInfo}; -use ide_db::{assists::GroupLabel, syntax_helpers::suggest_name}; +use ide_db::{ + assists::GroupLabel, + syntax_helpers::{suggest_name, LexedStr}, +}; use syntax::{ ast::{ self, edit::IndentLevel, edit_in_place::Indent, make, syntax_factory::SyntaxFactory, @@ -320,24 +323,58 @@ impl ExtractionKind { ctx: &AssistContext<'_>, to_extract: &ast::Expr, ) -> (String, SyntaxNode) { - let field_shorthand = to_extract - .syntax() - .parent() - .and_then(ast::RecordExprField::cast) - .filter(|field| field.name_ref().is_some()); - let (var_name, expr_replace) = match field_shorthand { - Some(field) => (field.to_string(), field.syntax().clone()), - None => { - (suggest_name::for_variable(to_extract, &ctx.sema), to_extract.syntax().clone()) + // We only do this sort of extraction for fields because they should have lowercase names + if let ExtractionKind::Variable = self { + let field_shorthand = to_extract + .syntax() + .parent() + .and_then(ast::RecordExprField::cast) + .filter(|field| field.name_ref().is_some()); + + if let Some(field) = field_shorthand { + return (field.to_string(), field.syntax().clone()); } + } + + let var_name = if let Some(literal_name) = get_literal_name(ctx, to_extract) { + literal_name + } else { + suggest_name::for_variable(to_extract, &ctx.sema) }; let var_name = match self { - ExtractionKind::Variable => var_name, + ExtractionKind::Variable => var_name.to_lowercase(), ExtractionKind::Constant | ExtractionKind::Static => var_name.to_uppercase(), }; - (var_name, expr_replace) + (var_name, to_extract.syntax().clone()) + } +} + +fn get_literal_name(ctx: &AssistContext<'_>, expr: &ast::Expr) -> Option { + let literal = match expr { + ast::Expr::Literal(literal) => literal, + _ => return None, + }; + let inner = match literal.kind() { + ast::LiteralKind::String(string) => string.value().ok()?.into_owned(), + ast::LiteralKind::ByteString(byte_string) => { + String::from_utf8(byte_string.value().ok()?.into_owned()).ok()? + } + ast::LiteralKind::CString(cstring) => { + String::from_utf8(cstring.value().ok()?.into_owned()).ok()? + } + _ => return None, + }; + + // Entirely arbitrary + if inner.len() > 32 { + return None; + } + + match LexedStr::single_token(ctx.file_id().edition(), &inner) { + Some((SyntaxKind::IDENT, None)) => Some(inner), + _ => None, } } @@ -493,7 +530,7 @@ fn main() { "#, r#" fn main() { - let $0var_name = "hello"; + let $0hello = "hello"; } "#, "Extract into variable", @@ -588,7 +625,7 @@ fn main() { "#, r#" fn main() { - const $0VAR_NAME: &str = "hello"; + const $0HELLO: &str = "hello"; } "#, "Extract into constant", @@ -683,7 +720,7 @@ fn main() { "#, r#" fn main() { - static $0VAR_NAME: &str = "hello"; + static $0HELLO: &str = "hello"; } "#, "Extract into static", @@ -2479,4 +2516,120 @@ fn foo() { "Extract into variable", ); } + + #[test] + fn extract_string_literal() { + check_assist_by_label( + extract_variable, + r#" +struct Entry(&str); +fn foo() { + let entry = Entry($0"Hello"$0); +} +"#, + r#" +struct Entry(&str); +fn foo() { + let $0hello = "Hello"; + let entry = Entry(hello); +} +"#, + "Extract into variable", + ); + + check_assist_by_label( + extract_variable, + r#" +struct Entry(&str); +fn foo() { + let entry = Entry($0"Hello"$0); +} +"#, + r#" +struct Entry(&str); +fn foo() { + const $0HELLO: &str = "Hello"; + let entry = Entry(HELLO); +} +"#, + "Extract into constant", + ); + + check_assist_by_label( + extract_variable, + r#" +struct Entry(&str); +fn foo() { + let entry = Entry($0"Hello"$0); +} +"#, + r#" +struct Entry(&str); +fn foo() { + static $0HELLO: &str = "Hello"; + let entry = Entry(HELLO); +} +"#, + "Extract into static", + ); + } + + #[test] + fn extract_variable_string_literal_use_field_shorthand() { + // When field shorthand is available, it should + // only be used when extracting into a variable + check_assist_by_label( + extract_variable, + r#" +struct Entry { message: &str } +fn foo() { + let entry = Entry { message: $0"Hello"$0 }; +} +"#, + r#" +struct Entry { message: &str } +fn foo() { + let $0message = "Hello"; + let entry = Entry { message }; +} +"#, + "Extract into variable", + ); + + check_assist_by_label( + extract_variable, + r#" +struct Entry { message: &str } +fn foo() { + let entry = Entry { message: $0"Hello"$0 }; +} +"#, + r#" +struct Entry { message: &str } +fn foo() { + const $0HELLO: &str = "Hello"; + let entry = Entry { message: HELLO }; +} +"#, + "Extract into constant", + ); + + check_assist_by_label( + extract_variable, + r#" +struct Entry { message: &str } +fn foo() { + let entry = Entry { message: $0"Hello"$0 }; +} +"#, + r#" +struct Entry { message: &str } +fn foo() { + static $0HELLO: &str = "Hello"; + let entry = Entry { message: HELLO }; +} +"#, + "Extract into static", + ); + } }