Fix renaming mod in use tree

This commit is contained in:
unexge 2020-06-12 00:40:59 +03:00
parent 1ce8c2b5a0
commit ce29d172a5

View File

@ -1,11 +1,14 @@
//! FIXME: write short doc here //! FIXME: write short doc here
use hir::{ModuleSource, Semantics}; use hir::{Module, ModuleDef, ModuleSource, Semantics};
use ra_db::{RelativePathBuf, SourceDatabaseExt}; use ra_db::{RelativePathBuf, SourceDatabaseExt};
use ra_ide_db::RootDatabase; use ra_ide_db::{
defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
RootDatabase,
};
use ra_syntax::{ use ra_syntax::{
algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind, algo::find_node_at_offset, ast, ast::NameOwner, ast::TypeAscriptionOwner,
AstNode, SyntaxKind, SyntaxNode, SyntaxToken, lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
}; };
use ra_text_edit::TextEdit; use ra_text_edit::TextEdit;
use std::convert::TryInto; use std::convert::TryInto;
@ -30,10 +33,8 @@ pub(crate) fn rename(
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);
let syntax = source_file.syntax(); let syntax = source_file.syntax();
if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) { if let Some(module) = find_module_at_offset(&sema, position, syntax) {
let range = ast_name.syntax().text_range(); rename_mod(db, position, module, new_name)
rename_mod(&sema, &ast_name, &ast_module, position, new_name)
.map(|info| RangeInfo::new(range, info))
} else if let Some(self_token) = } else if let Some(self_token) =
syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
{ {
@ -43,13 +44,32 @@ pub(crate) fn rename(
} }
} }
fn find_name_and_module_at_offset( fn find_module_at_offset(
syntax: &SyntaxNode, sema: &Semantics<RootDatabase>,
position: FilePosition, position: FilePosition,
) -> Option<(ast::Name, ast::Module)> { syntax: &SyntaxNode,
let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; ) -> Option<Module> {
let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?;
Some((ast_name, ast_module))
let module = match_ast! {
match (ident.parent()) {
ast::NameRef(name_ref) => {
match classify_name_ref(sema, &name_ref)? {
NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
_ => return None,
}
},
ast::Name(name) => {
match classify_name(&sema, &name)? {
NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module,
_ => return None,
}
},
_ => return None,
}
};
Some(module)
} }
fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit {
@ -77,49 +97,50 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
} }
fn rename_mod( fn rename_mod(
sema: &Semantics<RootDatabase>, db: &RootDatabase,
ast_name: &ast::Name,
ast_module: &ast::Module,
position: FilePosition, position: FilePosition,
module: Module,
new_name: &str, new_name: &str,
) -> Option<SourceChange> { ) -> Option<RangeInfo<SourceChange>> {
let mut source_file_edits = Vec::new(); let mut source_file_edits = Vec::new();
let mut file_system_edits = Vec::new(); let mut file_system_edits = Vec::new();
if let Some(module) = sema.to_def(ast_module) {
let src = module.definition_source(sema.db); let src = module.definition_source(db);
let file_id = src.file_id.original_file(sema.db); let file_id = src.file_id.original_file(db);
match src.value { match src.value {
ModuleSource::SourceFile(..) => { ModuleSource::SourceFile(..) => {
let mod_path: RelativePathBuf = sema.db.file_relative_path(file_id); let mod_path: RelativePathBuf = db.file_relative_path(file_id);
// mod is defined in path/to/dir/mod.rs // mod is defined in path/to/dir/mod.rs
let dst = if mod_path.file_stem() == Some("mod") { let dst = if mod_path.file_stem() == Some("mod") {
format!("../{}/mod.rs", new_name) format!("../{}/mod.rs", new_name)
} else { } else {
format!("{}.rs", new_name) format!("{}.rs", new_name)
}; };
let move_file = let move_file =
FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst }; FileSystemEdit::MoveFile { src: file_id, anchor: position.file_id, dst };
file_system_edits.push(move_file); file_system_edits.push(move_file);
}
ModuleSource::Module(..) => {}
} }
ModuleSource::Module(..) => {}
} }
let edit = SourceFileEdit { if let Some(src) = module.declaration_source(db) {
file_id: position.file_id, let file_id = src.file_id.original_file(db);
edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), let name = src.value.name()?;
}; let edit = SourceFileEdit {
source_file_edits.push(edit); file_id: file_id,
edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
if let Some(RangeInfo { range: _, info: refs }) = find_all_refs(sema.db, position, None) { };
let ref_edits = refs source_file_edits.push(edit);
.references
.into_iter()
.map(|reference| source_edit_from_reference(reference, new_name));
source_file_edits.extend(ref_edits);
} }
Some(SourceChange::from_edits(source_file_edits, file_system_edits)) let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?;
let ref_edits = refs
.references
.into_iter()
.map(|reference| source_edit_from_reference(reference, new_name));
source_file_edits.extend(ref_edits);
Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
} }
fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> { fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
@ -666,6 +687,76 @@ fn test_rename_mod() {
"###); "###);
} }
#[test]
fn test_rename_mod_in_use_tree() {
let (analysis, position) = analysis_and_position(
r#"
//- /main.rs
pub mod foo;
pub mod bar;
fn main() {}
//- /foo.rs
pub struct FooContent;
//- /bar.rs
use crate::foo<|>::FooContent;
"#,
);
let new_name = "qux";
let source_change = analysis.rename(position, new_name).unwrap();
assert_debug_snapshot!(&source_change,
@r###"
Some(
RangeInfo {
range: 11..14,
info: SourceChange {
source_file_edits: [
SourceFileEdit {
file_id: FileId(
1,
),
edit: TextEdit {
indels: [
Indel {
insert: "qux",
delete: 8..11,
},
],
},
},
SourceFileEdit {
file_id: FileId(
3,
),
edit: TextEdit {
indels: [
Indel {
insert: "qux",
delete: 11..14,
},
],
},
},
],
file_system_edits: [
MoveFile {
src: FileId(
2,
),
anchor: FileId(
3,
),
dst: "qux.rs",
},
],
is_snippet: false,
},
},
)
"###);
}
#[test] #[test]
fn test_rename_mod_in_dir() { fn test_rename_mod_in_dir() {
let (analysis, position) = analysis_and_position( let (analysis, position) = analysis_and_position(