From 2ab79c6f4d972796d3fd9f4b7fae3342a551f5e8 Mon Sep 17 00:00:00 2001 From: Jess Balint Date: Wed, 20 May 2020 18:37:09 -0500 Subject: [PATCH] Assist: replace anonymous lifetime with a named one (fixes #4523) --- .../handlers/change_lifetime_anon_to_named.rs | 123 ++++++++++++++++++ crates/ra_assists/src/lib.rs | 2 + docs/user/assists.md | 24 ++++ 3 files changed, 149 insertions(+) create mode 100644 crates/ra_assists/src/handlers/change_lifetime_anon_to_named.rs 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 0000000000..63f0a7dabf --- /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"#, + ); + } +} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 464bc03dde..3f8f7ffbfc 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -112,6 +112,7 @@ mod handlers { mod add_turbo_fish; mod apply_demorgan; mod auto_import; + mod change_lifetime_anon_to_named; mod change_return_type_to_result; mod change_visibility; mod early_return; @@ -151,6 +152,7 @@ mod handlers { add_turbo_fish::add_turbo_fish, apply_demorgan::apply_demorgan, auto_import::auto_import, + change_lifetime_anon_to_named::change_lifetime_anon_to_named, change_return_type_to_result::change_return_type_to_result, change_visibility::change_visibility, early_return::convert_to_guarded_return, diff --git a/docs/user/assists.md b/docs/user/assists.md index 4ad7ea59d2..29b64330e9 100644 --- a/docs/user/assists.md +++ b/docs/user/assists.md @@ -259,6 +259,30 @@ fn main() { } ``` +## `change_lifetime_anon_to_named` + +Change an anonymous lifetime to a named lifetime. + +```rust +// BEFORE +impl Cursor<'_<|>> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } +} + +// AFTER +impl<'a> Cursor<'a> { + fn node(self) -> &SyntaxNode { + match self { + Cursor::Replace(node) | Cursor::Before(node) => node, + } + } +} +``` + ## `change_return_type_to_result` Change the function's return type to Result.