From 2ab79c6f4d972796d3fd9f4b7fae3342a551f5e8 Mon Sep 17 00:00:00 2001 From: Jess Balint Date: Wed, 20 May 2020 18:37:09 -0500 Subject: Assist: replace anonymous lifetime with a named one (fixes #4523) --- .../src/handlers/change_lifetime_anon_to_named.rs | 123 +++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs (limited to 'crates/ra_assists/src/handlers') diff --git a/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs b/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs new file mode 100644 index 000000000..63f0a7dab --- /dev/null +++ b/crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs @@ -0,0 +1,123 @@ +use crate::{AssistContext, AssistId, Assists}; +use ra_syntax::{ast, ast::{TypeParamsOwner}, AstNode, SyntaxKind}; + +/// Assist: change_lifetime_anon_to_named +/// +/// Change an anonymous lifetime to a named lifetime. +/// +/// ``` +/// impl Cursor<'_<|>> { +/// fn node(self) -> &SyntaxNode { +/// match self { +/// Cursor::Replace(node) | Cursor::Before(node) => node, +/// } +/// } +/// } +/// ``` +/// -> +/// ``` +/// impl<'a> Cursor<'a> { +/// fn node(self) -> &SyntaxNode { +/// match self { +/// Cursor::Replace(node) | Cursor::Before(node) => node, +/// } +/// } +/// } +/// ``` +// TODO : How can we handle renaming any one of multiple anonymous lifetimes? +pub(crate) fn change_lifetime_anon_to_named(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let lifetime_token = ctx.find_token_at_offset(SyntaxKind::LIFETIME)?; + let lifetime_arg = ast::LifetimeArg::cast(lifetime_token.parent())?; + if lifetime_arg.syntax().text() != "'_" { + return None; + } + let next_token = lifetime_token.next_token()?; + if next_token.kind() != SyntaxKind::R_ANGLE { + // only allow naming the last anonymous lifetime + return None; + } + match lifetime_arg.syntax().ancestors().find_map(ast::ImplDef::cast) { + Some(impl_def) => { + // get the `impl` keyword so we know where to add the lifetime argument + let impl_kw = impl_def.syntax().first_child_or_token()?.into_token()?; + if impl_kw.kind() != SyntaxKind::IMPL_KW { + return None; + } + acc.add( + AssistId("change_lifetime_anon_to_named"), + "Give anonymous lifetime a name", + lifetime_arg.syntax().text_range(), + |builder| { + match impl_def.type_param_list() { + Some(type_params) => { + builder.insert( + (u32::from(type_params.syntax().text_range().end()) - 1).into(), + ", 'a", + ); + }, + None => { + builder.insert( + impl_kw.text_range().end(), + "<'a>", + ); + }, + } + builder.replace(lifetime_arg.syntax().text_range(), "'a"); + }, + ) + } + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{check_assist, check_assist_not_applicable}; + + #[test] + fn test_example_case() { + check_assist( + change_lifetime_anon_to_named, + r#"impl Cursor<'_<|>> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } + }"#, + r#"impl<'a> Cursor<'a> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } + }"#, + ); + } + + #[test] + fn test_example_case_simplified() { + check_assist( + change_lifetime_anon_to_named, + r#"impl Cursor<'_<|>> {"#, + r#"impl<'a> Cursor<'a> {"#, + ); + } + + #[test] + fn test_not_applicable() { + check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'_><|> {"#); + check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<|><'_> {"#); + check_assist_not_applicable(change_lifetime_anon_to_named, r#"impl Cursor<'a<|>> {"#); + } + + #[test] + fn test_with_type_parameter() { + check_assist( + change_lifetime_anon_to_named, + r#"impl Cursor>"#, + r#"impl Cursor"#, + ); + } +} -- cgit v1.2.3