Merge pull request #18690 from Giga-Bowser/extract-variable-string

feat: Use string literal contents as a name when extracting into variable
This commit is contained in:
Lukas Wirth 2024-12-16 10:29:05 +00:00 committed by GitHub
commit 61c222e1af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,5 +1,8 @@
use hir::{HirDisplay, TypeInfo}; 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::{ use syntax::{
ast::{ ast::{
self, edit::IndentLevel, edit_in_place::Indent, make, syntax_factory::SyntaxFactory, self, edit::IndentLevel, edit_in_place::Indent, make, syntax_factory::SyntaxFactory,
@ -320,24 +323,58 @@ impl ExtractionKind {
ctx: &AssistContext<'_>, ctx: &AssistContext<'_>,
to_extract: &ast::Expr, to_extract: &ast::Expr,
) -> (String, SyntaxNode) { ) -> (String, SyntaxNode) {
let field_shorthand = to_extract // We only do this sort of extraction for fields because they should have lowercase names
.syntax() if let ExtractionKind::Variable = self {
.parent() let field_shorthand = to_extract
.and_then(ast::RecordExprField::cast) .syntax()
.filter(|field| field.name_ref().is_some()); .parent()
let (var_name, expr_replace) = match field_shorthand { .and_then(ast::RecordExprField::cast)
Some(field) => (field.to_string(), field.syntax().clone()), .filter(|field| field.name_ref().is_some());
None => {
(suggest_name::for_variable(to_extract, &ctx.sema), to_extract.syntax().clone()) 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 { let var_name = match self {
ExtractionKind::Variable => var_name, ExtractionKind::Variable => var_name.to_lowercase(),
ExtractionKind::Constant | ExtractionKind::Static => var_name.to_uppercase(), 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<String> {
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#" r#"
fn main() { fn main() {
let $0var_name = "hello"; let $0hello = "hello";
} }
"#, "#,
"Extract into variable", "Extract into variable",
@ -588,7 +625,7 @@ fn main() {
"#, "#,
r#" r#"
fn main() { fn main() {
const $0VAR_NAME: &str = "hello"; const $0HELLO: &str = "hello";
} }
"#, "#,
"Extract into constant", "Extract into constant",
@ -683,7 +720,7 @@ fn main() {
"#, "#,
r#" r#"
fn main() { fn main() {
static $0VAR_NAME: &str = "hello"; static $0HELLO: &str = "hello";
} }
"#, "#,
"Extract into static", "Extract into static",
@ -2479,4 +2516,120 @@ fn foo() {
"Extract into variable", "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",
);
}
} }