From d7d8bfc472fcb009c605b2ffba56cf04c4bc73a2 Mon Sep 17 00:00:00 2001 From: zbsz Date: Sun, 3 May 2020 22:12:18 -0700 Subject: [PATCH] Add 'self to parameter' renaming. --- crates/ra_ide/src/references/rename.rs | 211 ++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 5 deletions(-) diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 2cbb82c1a4..410dae75cb 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs @@ -4,14 +4,16 @@ use hir::{ModuleSource, Semantics}; use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; use ra_ide_db::RootDatabase; use ra_syntax::{ - algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, + algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, + AstNode, SyntaxKind, SyntaxNode, SyntaxToken, }; use ra_text_edit::TextEdit; +use std::convert::TryInto; use test_utils::tested_by; use crate::{ references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, - SourceChange, SourceFileEdit, TextRange, + SourceChange, SourceFileEdit, TextRange, TextSize, }; pub(crate) fn rename( @@ -21,17 +23,21 @@ pub(crate) fn rename( ) -> Option> { match lex_single_valid_syntax_kind(new_name)? { SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), + SyntaxKind::SELF_KW => return rename_to_self(db, position), _ => return None, } let sema = Semantics::new(db); let source_file = sema.parse(position.file_id); - if let Some((ast_name, ast_module)) = - find_name_and_module_at_offset(source_file.syntax(), position) - { + let syntax = source_file.syntax(); + if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { let range = ast_name.syntax().text_range(); rename_mod(&sema, &ast_name, &ast_module, position, new_name) .map(|info| RangeInfo::new(range, info)) + } else if let Some(self_token) = + syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) + { + rename_self_to_param(db, position, self_token, new_name) } else { rename_reference(sema.db, position, new_name) } @@ -125,6 +131,112 @@ fn rename_mod( Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) } +fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syn = source_file.syntax(); + + let fn_def = find_node_at_offset::(syn, position.offset)?; + let params = fn_def.param_list()?; + if params.self_param().is_some() { + return None; // method already has self param + } + let first_param = params.params().next()?; + let mutable = match first_param.ascribed_type() { + Some(ast::TypeRef::ReferenceType(rt)) => rt.mut_token().is_some(), + _ => return None, // not renaming other types + }; + + let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; + + let param_range = first_param.syntax().text_range(); + let (param_ref, usages): (Vec, Vec) = refs + .into_iter() + .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); + + if param_ref.is_empty() { + return None; + } + + let mut edits = usages + .into_iter() + .map(|reference| source_edit_from_reference(reference, "self")) + .collect::>(); + + edits.push(SourceFileEdit { + file_id: position.file_id, + edit: TextEdit::replace( + param_range, + String::from(if mutable { "&mut self" } else { "&self" }), + ), + }); + + Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) +} + +fn text_edit_from_self_param( + syn: &SyntaxNode, + self_param: &ast::SelfParam, + new_name: &str, +) -> Option { + fn target_type_name(impl_def: &ast::ImplDef) -> Option { + if let Some(ast::TypeRef::PathType(p)) = impl_def.target_type() { + return Some(p.path()?.segment()?.name_ref()?.text().to_string()); + } + None + } + + let impl_def = + find_node_at_offset::(syn, self_param.syntax().text_range().start())?; + let type_name = target_type_name(&impl_def)?; + + let mut replacement_text = String::from(new_name); + replacement_text.push_str(": "); + replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut ")); + replacement_text.push_str(type_name.as_str()); + + Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) +} + +fn rename_self_to_param( + db: &RootDatabase, + position: FilePosition, + self_token: SyntaxToken, + new_name: &str, +) -> Option> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syn = source_file.syntax(); + + let text = db.file_text(position.file_id); + let fn_def = find_node_at_offset::(syn, position.offset)?; + let search_range = fn_def.syntax().text_range(); + + let mut edits: Vec = vec![]; + + for (idx, _) in text.match_indices("self") { + let offset: TextSize = idx.try_into().unwrap(); + if !search_range.contains_inclusive(offset) { + continue; + } + if let Some(ref usage) = + syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) + { + let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { + text_edit_from_self_param(syn, self_param, new_name)? + } else { + TextEdit::replace(usage.text_range(), String::from(new_name)) + }; + edits.push(SourceFileEdit { file_id: position.file_id, edit }); + } + } + + let range = ast::SelfParam::cast(self_token.parent()) + .map_or(self_token.text_range(), |p| p.syntax().text_range()); + + Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits))) +} + fn rename_reference( db: &RootDatabase, position: FilePosition, @@ -774,6 +886,95 @@ mod tests { ); } + #[test] + fn test_parameter_to_self() { + test_rename( + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(foo<|>: &mut Foo) -> i32 { + foo.i + } + } + "#, + "self", + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(&mut self) -> i32 { + self.i + } + } + "#, + ); + } + + #[test] + fn test_self_to_parameter() { + test_rename( + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(&mut <|>self) -> i32 { + self.i + } + } + "#, + "foo", + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(foo: &mut Foo) -> i32 { + foo.i + } + } + "#, + ); + } + + #[test] + fn test_self_in_path_to_parameter() { + test_rename( + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(&self) -> i32 { + let self_var = 1; + self<|>.i + } + } + "#, + "foo", + r#" + struct Foo { + i: i32, + } + + impl Foo { + fn f(foo: &Foo) -> i32 { + let self_var = 1; + foo.i + } + } + "#, + ); + } + fn test_rename(text: &str, new_name: &str, expected: &str) { let (analysis, position) = single_file_with_position(text); let source_change = analysis.rename(position, new_name).unwrap();