Auto merge of #12387 - 00nktk:fix-mod-rename, r=Veykril

fix(ide-db): correct single-file module rename

Fixes a bug where rust-analyzer would emit `WorkspaceEdit`s with paths to dirs instead of files for the following project layout.

lib.rs
```rust
mod foo;
```

foo.rs
```rust
mod bar {
    struct Bar;
}
```
Also fixes emitted paths for modules with mod.rs.
The bug resulted in panic in helix editor when attempting to rename a module.
This commit is contained in:
bors 2022-05-30 11:29:55 +00:00
commit bd0c2344f2
3 changed files with 42 additions and 8 deletions

View File

@ -39,6 +39,11 @@ pub fn is_mod_rs(self, db: &dyn HirDatabase) -> bool {
}
}
pub fn is_inline(self, db: &dyn HirDatabase) -> bool {
let def_map = self.id.def_map(db.upcast());
def_map[self.id.local_id].origin.is_inline()
}
/// Returns a node which declares this module, either a `mod foo;` or a `mod foo {}`.
/// `None` for the crate root.
pub fn declaration_source(self, db: &dyn HirDatabase) -> Option<InFile<ast::Module>> {

View File

@ -180,15 +180,33 @@ fn rename_mod(
let InFile { file_id, value: def_source } = module.definition_source(sema.db);
if let ModuleSource::SourceFile(..) = def_source {
let anchor = file_id.original_file(sema.db);
// not mod.rs and doesn't has children, rename file only
if !module.is_mod_rs(sema.db) && module.children(sema.db).next().is_none() {
let is_mod_rs = module.is_mod_rs(sema.db);
let has_detached_child = module.children(sema.db).any(|child| !child.is_inline(sema.db));
// Module exists in a named file
if !is_mod_rs {
let path = format!("{}.rs", new_name);
let dst = AnchoredPathBuf { anchor, path };
source_change.push_file_system_edit(FileSystemEdit::MoveFile { src: anchor, dst })
} else if let Some(mod_name) = module.name(sema.db) {
// is mod.rs or has children, rename dir
let src = AnchoredPathBuf { anchor, path: mod_name.to_string() };
let dst = AnchoredPathBuf { anchor, path: new_name.to_string() };
}
// Rename the dir if:
// - Module source is in mod.rs
// - Module has submodules defined in separate files
let dir_paths = match (is_mod_rs, has_detached_child, module.name(sema.db)) {
// Go up one level since the anchor is inside the dir we're trying to rename
(true, _, Some(mod_name)) => {
Some((format!("../{}", mod_name), format!("../{}", new_name)))
}
// The anchor is on the same level as target dir
(false, true, Some(mod_name)) => Some((mod_name.to_string(), new_name.to_string())),
_ => None,
};
if let Some((src, dst)) = dir_paths {
let src = AnchoredPathBuf { anchor, path: src };
let dst = AnchoredPathBuf { anchor, path: dst };
source_change.push_file_system_edit(FileSystemEdit::MoveDir {
src,
src_id: anchor,

View File

@ -973,7 +973,7 @@ fn test_rename_mod_in_dir() {
anchor: FileId(
1,
),
path: "foo",
path: "../foo",
},
src_id: FileId(
1,
@ -982,7 +982,7 @@ fn test_rename_mod_in_dir() {
anchor: FileId(
1,
),
path: "foo2",
path: "../foo2",
},
},
],
@ -1158,6 +1158,17 @@ fn test_rename_mod_recursive() {
},
},
file_system_edits: [
MoveFile {
src: FileId(
1,
),
dst: AnchoredPathBuf {
anchor: FileId(
1,
),
path: "foo2.rs",
},
},
MoveDir {
src: AnchoredPathBuf {
anchor: FileId(