]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/references/rename.rs
Wrap remaining self/super/crate in Name{Ref}
[rust.git] / crates / ide / src / references / rename.rs
index 44dfa9a0ed33442de1716a68a797edd5e6cf7397..4df189c98196678ca2520e60fc8b18f2e61f515f 100644 (file)
@@ -1,27 +1,24 @@
 //! FIXME: write short doc here
-use std::{
-    convert::TryInto,
-    error::Error,
-    fmt::{self, Display},
-};
+use std::fmt::{self, Display};
 
 use hir::{Module, ModuleDef, ModuleSource, Semantics};
-use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt};
 use ide_db::{
+    base_db::{AnchoredPathBuf, FileId, FileRange},
     defs::{Definition, NameClass, NameRefClass},
+    search::FileReference,
     RootDatabase,
 };
 use syntax::{
     algo::find_node_at_offset,
     ast::{self, NameOwner},
-    lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T,
+    lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, T,
 };
 use test_utils::mark;
 use text_edit::TextEdit;
 
 use crate::{
-    FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult,
-    SourceChange, SourceFileEdit, TextRange, TextSize,
+    FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, ReferenceSearchResult, SourceChange,
+    TextRange,
 };
 
 type RenameResult<T> = Result<T, RenameError>;
@@ -34,8 +31,6 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
     }
 }
 
-impl Error for RenameError {}
-
 macro_rules! format_err {
     ($fmt:expr) => {RenameError(format!($fmt))};
     ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))}
@@ -54,13 +49,9 @@ pub(crate) fn prepare_rename(
     let syntax = source_file.syntax();
     if let Some(module) = find_module_at_offset(&sema, position, syntax) {
         rename_mod(&sema, position, module, "dummy")
-    } else if let Some(self_token) =
-        syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
-    {
-        rename_self_to_param(&sema, position, self_token, "dummy")
     } else {
         let RangeInfo { range, .. } = find_all_refs(&sema, position)?;
-        Ok(RangeInfo::new(range, SourceChange::from(vec![])))
+        Ok(RangeInfo::new(range, SourceChange::default()))
     }
     .map(|info| RangeInfo::new(info.range, ()))
 }
@@ -84,10 +75,6 @@ pub(crate) fn rename_with_semantics(
 
     if let Some(module) = find_module_at_offset(&sema, position, syntax) {
         rename_mod(&sema, position, module, new_name)
-    } else if let Some(self_token) =
-        syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
-    {
-        rename_self_to_param(&sema, position, self_token, new_name)
     } else {
         rename_reference(&sema, position, new_name)
     }
@@ -110,7 +97,7 @@ pub(crate) fn will_rename_file(
     Some(change)
 }
 
-#[derive(Debug, PartialEq)]
+#[derive(Copy, Clone, Debug, PartialEq)]
 enum IdentifierKind {
     Ident,
     Lifetime,
@@ -173,39 +160,45 @@ fn find_all_refs(
         .ok_or_else(|| format_err!("No references found at position"))
 }
 
-fn source_edit_from_reference(
+fn source_edit_from_references(
     sema: &Semantics<RootDatabase>,
-    reference: Reference,
+    file_id: FileId,
+    references: &[FileReference],
     new_name: &str,
-) -> SourceFileEdit {
-    let mut replacement_text = String::new();
-    let range = match reference.kind {
-        ReferenceKind::FieldShorthandForField => {
-            mark::hit!(test_rename_struct_field_for_shorthand);
-            replacement_text.push_str(new_name);
-            replacement_text.push_str(": ");
-            TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
-        }
-        ReferenceKind::FieldShorthandForLocal => {
-            mark::hit!(test_rename_local_for_field_shorthand);
-            replacement_text.push_str(": ");
-            replacement_text.push_str(new_name);
-            TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
-        }
-        ReferenceKind::RecordFieldExprOrPat => {
-            mark::hit!(test_rename_field_expr_pat);
-            replacement_text.push_str(new_name);
-            edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name)
-        }
-        _ => {
-            replacement_text.push_str(new_name);
-            reference.file_range.range
-        }
-    };
-    SourceFileEdit {
-        file_id: reference.file_range.file_id,
-        edit: TextEdit::replace(range, replacement_text),
+) -> (FileId, TextEdit) {
+    let mut edit = TextEdit::builder();
+    for reference in references {
+        let mut replacement_text = String::new();
+        let range = match reference.kind {
+            ReferenceKind::FieldShorthandForField => {
+                mark::hit!(test_rename_struct_field_for_shorthand);
+                replacement_text.push_str(new_name);
+                replacement_text.push_str(": ");
+                TextRange::new(reference.range.start(), reference.range.start())
+            }
+            ReferenceKind::FieldShorthandForLocal => {
+                mark::hit!(test_rename_local_for_field_shorthand);
+                replacement_text.push_str(": ");
+                replacement_text.push_str(new_name);
+                TextRange::new(reference.range.end(), reference.range.end())
+            }
+            ReferenceKind::RecordFieldExprOrPat => {
+                mark::hit!(test_rename_field_expr_pat);
+                replacement_text.push_str(new_name);
+                edit_text_range_for_record_field_expr_or_pat(
+                    sema,
+                    FileRange { file_id, range: reference.range },
+                    new_name,
+                )
+            }
+            _ => {
+                replacement_text.push_str(new_name);
+                reference.range
+            }
+        };
+        edit.replace(range, replacement_text);
     }
+    (file_id, edit.finish())
 }
 
 fn edit_text_range_for_record_field_expr_or_pat(
@@ -245,8 +238,8 @@ fn rename_mod(
     if IdentifierKind::Ident != check_identifier(new_name)? {
         bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
     }
-    let mut source_file_edits = Vec::new();
-    let mut file_system_edits = Vec::new();
+
+    let mut source_change = SourceChange::default();
 
     let src = module.definition_source(sema.db);
     let file_id = src.file_id.original_file(sema.db);
@@ -260,7 +253,7 @@ fn rename_mod(
             };
             let dst = AnchoredPathBuf { anchor: file_id, path };
             let move_file = FileSystemEdit::MoveFile { src: file_id, dst };
-            file_system_edits.push(move_file);
+            source_change.push_file_system_edit(move_file);
         }
         ModuleSource::Module(..) => {}
     }
@@ -268,21 +261,19 @@ fn rename_mod(
     if let Some(src) = module.declaration_source(sema.db) {
         let file_id = src.file_id.original_file(sema.db);
         let name = src.value.name().unwrap();
-        let edit = SourceFileEdit {
+        source_change.insert_source_edit(
             file_id,
-            edit: TextEdit::replace(name.syntax().text_range(), new_name.into()),
-        };
-        source_file_edits.push(edit);
+            TextEdit::replace(name.syntax().text_range(), new_name.into()),
+        );
     }
 
     let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
-    let ref_edits = refs
-        .references
-        .into_iter()
-        .map(|reference| source_edit_from_reference(sema, reference, new_name));
-    source_file_edits.extend(ref_edits);
+    let ref_edits = refs.references().iter().map(|(&file_id, references)| {
+        source_edit_from_references(sema, file_id, references, new_name)
+    });
+    source_change.extend(ref_edits);
 
-    Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
+    Ok(RangeInfo::new(range, source_change))
 }
 
 fn rename_to_self(
@@ -331,25 +322,16 @@ fn rename_to_self(
 
     let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
 
-    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() {
-        bail!("Parameter to rename not found");
-    }
-
-    let mut edits = usages
-        .into_iter()
-        .map(|reference| source_edit_from_reference(sema, reference, "self"))
-        .collect::<Vec<_>>();
-
-    edits.push(SourceFileEdit {
-        file_id: position.file_id,
-        edit: TextEdit::replace(param_range, String::from(self_param)),
-    });
+    let mut source_change = SourceChange::default();
+    source_change.extend(refs.references().iter().map(|(&file_id, references)| {
+        source_edit_from_references(sema, file_id, references, "self")
+    }));
+    source_change.insert_source_edit(
+        position.file_id,
+        TextEdit::replace(param_range, String::from(self_param)),
+    );
 
-    Ok(RangeInfo::new(range, SourceChange::from(edits)))
+    Ok(RangeInfo::new(range, source_change))
 }
 
 fn text_edit_from_self_param(
@@ -382,53 +364,50 @@ fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
 fn rename_self_to_param(
     sema: &Semantics<RootDatabase>,
     position: FilePosition,
-    self_token: SyntaxToken,
     new_name: &str,
+    ident_kind: IdentifierKind,
+    range: TextRange,
+    refs: ReferenceSearchResult,
 ) -> Result<RangeInfo<SourceChange>, RenameError> {
-    let ident_kind = check_identifier(new_name)?;
     match ident_kind {
         IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name),
         IdentifierKind::ToSelf => {
             // no-op
             mark::hit!(rename_self_to_self);
-            return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default()));
+            return Ok(RangeInfo::new(range, SourceChange::default()));
         }
         _ => (),
     }
     let source_file = sema.parse(position.file_id);
     let syn = source_file.syntax();
 
-    let text = sema.db.file_text(position.file_id);
     let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
         .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
-    let search_range = fn_def.syntax().text_range();
 
-    let mut edits: Vec<SourceFileEdit> = vec![];
+    let mut source_change = SourceChange::default();
+    if let Some(self_param) = fn_def.param_list().and_then(|it| it.self_param()) {
+        if self_param
+            .syntax()
+            .text_range()
+            .contains_range(refs.declaration().nav.focus_or_full_range())
+        {
+            let edit = text_edit_from_self_param(syn, &self_param, new_name)
+                .ok_or_else(|| format_err!("No target type found"))?;
+            source_change.insert_source_edit(position.file_id, edit);
+
+            source_change.extend(refs.references().iter().map(|(&file_id, references)| {
+                source_edit_from_references(sema, file_id, &references, new_name)
+            }));
+
+            if source_change.source_file_edits.len() > 1 && ident_kind == IdentifierKind::Underscore
+            {
+                bail!("Cannot rename reference to `_` as it is being referenced multiple times");
+            }
 
-    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() == T![self]) {
-            let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
-                text_edit_from_self_param(syn, self_param, new_name)
-                    .ok_or_else(|| format_err!("No target type found"))?
-            } else {
-                TextEdit::replace(usage.text_range(), String::from(new_name))
-            };
-            edits.push(SourceFileEdit { file_id: position.file_id, edit });
+            return Ok(RangeInfo::new(range, source_change));
         }
     }
-
-    if edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
-        bail!("Cannot rename reference to `_` as it is being referenced multiple times");
-    }
-
-    let range = ast::SelfParam::cast(self_token.parent())
-        .map_or(self_token.text_range(), |p| p.syntax().text_range());
-
-    Ok(RangeInfo::new(range, SourceChange::from(edits)))
+    Err(format_err!("Method has no self param"))
 }
 
 fn rename_reference(
@@ -451,8 +430,9 @@ fn rename_reference(
             mark::hit!(rename_not_an_ident_ref);
             bail!("Invalid name `{}`: not an identifier", new_name)
         }
-        (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => {
-            unreachable!("rename_self_to_param should've been called instead")
+        (_, ReferenceKind::SelfParam) => {
+            mark::hit!(rename_self_to_param);
+            return rename_self_to_param(sema, position, new_name, ident_kind, range, refs);
         }
         (IdentifierKind::ToSelf, _) => {
             mark::hit!(rename_to_self);
@@ -465,12 +445,12 @@ fn rename_reference(
         (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
     }
 
-    let edit = refs
-        .into_iter()
-        .map(|reference| source_edit_from_reference(sema, reference, new_name))
-        .collect::<Vec<_>>();
+    let mut source_change = SourceChange::default();
+    source_change.extend(refs.into_iter().map(|(file_id, references)| {
+        source_edit_from_references(sema, file_id, &references, new_name)
+    }));
 
-    Ok(RangeInfo::new(range, SourceChange::from(edit)))
+    Ok(RangeInfo::new(range, source_change))
 }
 
 #[cfg(test)]
@@ -493,8 +473,8 @@ fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
                 let mut text_edit_builder = TextEdit::builder();
                 let mut file_id: Option<FileId> = None;
                 for edit in source_change.info.source_file_edits {
-                    file_id = Some(edit.file_id);
-                    for indel in edit.edit.into_iter() {
+                    file_id = Some(edit.0);
+                    for indel in edit.1.into_iter() {
                         text_edit_builder.replace(indel.delete, indel.insert);
                     }
                 }
@@ -894,21 +874,18 @@ fn test_rename_mod() {
                 RangeInfo {
                     range: 4..7,
                     info: SourceChange {
-                        source_file_edits: [
-                            SourceFileEdit {
-                                file_id: FileId(
-                                    1,
-                                ),
-                                edit: TextEdit {
-                                    indels: [
-                                        Indel {
-                                            insert: "foo2",
-                                            delete: 4..7,
-                                        },
-                                    ],
-                                },
+                        source_file_edits: {
+                            FileId(
+                                1,
+                            ): TextEdit {
+                                indels: [
+                                    Indel {
+                                        insert: "foo2",
+                                        delete: 4..7,
+                                    },
+                                ],
                             },
-                        ],
+                        },
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
@@ -949,34 +926,28 @@ fn main() {}
                 RangeInfo {
                     range: 11..14,
                     info: SourceChange {
-                        source_file_edits: [
-                            SourceFileEdit {
-                                file_id: FileId(
-                                    0,
-                                ),
-                                edit: TextEdit {
-                                    indels: [
-                                        Indel {
-                                            insert: "quux",
-                                            delete: 8..11,
-                                        },
-                                    ],
-                                },
+                        source_file_edits: {
+                            FileId(
+                                0,
+                            ): TextEdit {
+                                indels: [
+                                    Indel {
+                                        insert: "quux",
+                                        delete: 8..11,
+                                    },
+                                ],
                             },
-                            SourceFileEdit {
-                                file_id: FileId(
-                                    2,
-                                ),
-                                edit: TextEdit {
-                                    indels: [
-                                        Indel {
-                                            insert: "quux",
-                                            delete: 11..14,
-                                        },
-                                    ],
-                                },
+                            FileId(
+                                2,
+                            ): TextEdit {
+                                indels: [
+                                    Indel {
+                                        insert: "quux",
+                                        delete: 11..14,
+                                    },
+                                ],
                             },
-                        ],
+                        },
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
@@ -1005,27 +976,24 @@ fn test_rename_mod_in_dir() {
 //- /lib.rs
 mod fo$0o;
 //- /foo/mod.rs
-// emtpy
+// empty
 "#,
             expect![[r#"
                 RangeInfo {
                     range: 4..7,
                     info: SourceChange {
-                        source_file_edits: [
-                            SourceFileEdit {
-                                file_id: FileId(
-                                    0,
-                                ),
-                                edit: TextEdit {
-                                    indels: [
-                                        Indel {
-                                            insert: "foo2",
-                                            delete: 4..7,
-                                        },
-                                    ],
-                                },
+                        source_file_edits: {
+                            FileId(
+                                0,
+                            ): TextEdit {
+                                indels: [
+                                    Indel {
+                                        insert: "foo2",
+                                        delete: 4..7,
+                                    },
+                                ],
                             },
-                        ],
+                        },
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
@@ -1055,27 +1023,24 @@ fn test_rename_unusually_nested_mod() {
 mod outer { mod fo$0o; }
 
 //- /outer/foo.rs
-// emtpy
+// empty
 "#,
             expect![[r#"
                 RangeInfo {
                     range: 16..19,
                     info: SourceChange {
-                        source_file_edits: [
-                            SourceFileEdit {
-                                file_id: FileId(
-                                    0,
-                                ),
-                                edit: TextEdit {
-                                    indels: [
-                                        Indel {
-                                            insert: "bar",
-                                            delete: 16..19,
-                                        },
-                                    ],
-                                },
+                        source_file_edits: {
+                            FileId(
+                                0,
+                            ): TextEdit {
+                                indels: [
+                                    Indel {
+                                        insert: "bar",
+                                        delete: 16..19,
+                                    },
+                                ],
                             },
-                        ],
+                        },
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
@@ -1134,34 +1099,28 @@ fn f() {
                 RangeInfo {
                     range: 8..11,
                     info: SourceChange {
-                        source_file_edits: [
-                            SourceFileEdit {
-                                file_id: FileId(
-                                    1,
-                                ),
-                                edit: TextEdit {
-                                    indels: [
-                                        Indel {
-                                            insert: "foo2",
-                                            delete: 8..11,
-                                        },
-                                    ],
-                                },
+                        source_file_edits: {
+                            FileId(
+                                0,
+                            ): TextEdit {
+                                indels: [
+                                    Indel {
+                                        insert: "foo2",
+                                        delete: 27..30,
+                                    },
+                                ],
                             },
-                            SourceFileEdit {
-                                file_id: FileId(
-                                    0,
-                                ),
-                                edit: TextEdit {
-                                    indels: [
-                                        Indel {
-                                            insert: "foo2",
-                                            delete: 27..30,
-                                        },
-                                    ],
-                                },
+                            FileId(
+                                1,
+                            ): TextEdit {
+                                indels: [
+                                    Indel {
+                                        insert: "foo2",
+                                        delete: 8..11,
+                                    },
+                                ],
                             },
-                        ],
+                        },
                         file_system_edits: [
                             MoveFile {
                                 src: FileId(
@@ -1378,6 +1337,7 @@ fn f(foo: &mut Foo) -> i32 {
 
     #[test]
     fn test_owned_self_to_parameter() {
+        mark::check!(rename_self_to_param);
         check(
             "foo",
             r#"