mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Add 'self to parameter' renaming.
This commit is contained in:
parent
cffa70be01
commit
d7d8bfc472
1 changed files with 206 additions and 5 deletions
|
@ -4,14 +4,16 @@ use hir::{ModuleSource, Semantics};
|
||||||
use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt};
|
use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt};
|
||||||
use ra_ide_db::RootDatabase;
|
use ra_ide_db::RootDatabase;
|
||||||
use ra_syntax::{
|
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 ra_text_edit::TextEdit;
|
||||||
|
use std::convert::TryInto;
|
||||||
use test_utils::tested_by;
|
use test_utils::tested_by;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
|
references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
|
||||||
SourceChange, SourceFileEdit, TextRange,
|
SourceChange, SourceFileEdit, TextRange, TextSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn rename(
|
pub(crate) fn rename(
|
||||||
|
@ -21,17 +23,21 @@ pub(crate) fn rename(
|
||||||
) -> Option<RangeInfo<SourceChange>> {
|
) -> Option<RangeInfo<SourceChange>> {
|
||||||
match lex_single_valid_syntax_kind(new_name)? {
|
match lex_single_valid_syntax_kind(new_name)? {
|
||||||
SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (),
|
SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (),
|
||||||
|
SyntaxKind::SELF_KW => return rename_to_self(db, position),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
}
|
}
|
||||||
|
|
||||||
let sema = Semantics::new(db);
|
let sema = Semantics::new(db);
|
||||||
let source_file = sema.parse(position.file_id);
|
let source_file = sema.parse(position.file_id);
|
||||||
if let Some((ast_name, ast_module)) =
|
let syntax = source_file.syntax();
|
||||||
find_name_and_module_at_offset(source_file.syntax(), position)
|
if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) {
|
||||||
{
|
|
||||||
let range = ast_name.syntax().text_range();
|
let range = ast_name.syntax().text_range();
|
||||||
rename_mod(&sema, &ast_name, &ast_module, position, new_name)
|
rename_mod(&sema, &ast_name, &ast_module, position, new_name)
|
||||||
.map(|info| RangeInfo::new(range, info))
|
.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 {
|
} else {
|
||||||
rename_reference(sema.db, position, new_name)
|
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))
|
Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
|
||||||
|
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::<ast::FnDef>(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<Reference>, Vec<Reference>) = 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::<Vec<_>>();
|
||||||
|
|
||||||
|
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<TextEdit> {
|
||||||
|
fn target_type_name(impl_def: &ast::ImplDef) -> Option<String> {
|
||||||
|
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::<ast::ImplDef>(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<RangeInfo<SourceChange>> {
|
||||||
|
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::<ast::FnDef>(syn, position.offset)?;
|
||||||
|
let search_range = fn_def.syntax().text_range();
|
||||||
|
|
||||||
|
let mut edits: Vec<SourceFileEdit> = 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(
|
fn rename_reference(
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
position: FilePosition,
|
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) {
|
fn test_rename(text: &str, new_name: &str, expected: &str) {
|
||||||
let (analysis, position) = single_file_with_position(text);
|
let (analysis, position) = single_file_with_position(text);
|
||||||
let source_change = analysis.rename(position, new_name).unwrap();
|
let source_change = analysis.rename(position, new_name).unwrap();
|
||||||
|
|
Loading…
Reference in a new issue