//! Renaming functionality.
//!
//! This is mostly front-end for [`ide_db::rename`], but it also includes the
//! tests. This module also implements a couple of magic tricks, like renaming
//! `self` and to `self` (to switch between associated function and method).

use hir::{AsAssocItem, InFile, Semantics};
use ide_db::{
    base_db::FileId,
    defs::{Definition, NameClass, NameRefClass},
    rename::{bail, format_err, source_edit_from_references, IdentifierKind},
    RootDatabase,
};
use itertools::Itertools;
use stdx::{always, never};
use syntax::{ast, AstNode, SyntaxNode};

use text_edit::TextEdit;

use crate::{FilePosition, RangeInfo, SourceChange};

pub use ide_db::rename::RenameError;

type RenameResult<T> = Result<T, RenameError>;

/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is
/// being targeted for a rename.
pub(crate) fn prepare_rename(
    db: &RootDatabase,
    position: FilePosition,
) -> RenameResult<RangeInfo<()>> {
    let sema = Semantics::new(db);
    let source_file = sema.parse(position.file_id);
    let syntax = source_file.syntax();

    let res = find_definitions(&sema, syntax, position)?
        .map(|(name_like, def)| {
            // ensure all ranges are valid

            if def.range_for_rename(&sema).is_none() {
                bail!("No references found at position")
            }
            let frange = sema.original_range(name_like.syntax());

            always!(
                frange.range.contains_inclusive(position.offset)
                    && frange.file_id == position.file_id
            );
            Ok(frange.range)
        })
        .reduce(|acc, cur| match (acc, cur) {
            // ensure all ranges are the same
            (Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),
            (Err(e), _) => Err(e),
            _ => bail!("inconsistent text range"),
        });

    match res {
        // ensure at least one definition was found
        Some(res) => res.map(|range| RangeInfo::new(range, ())),
        None => bail!("No references found at position"),
    }
}

// Feature: Rename
//
// Renames the item below the cursor and all of its references
//
// |===
// | Editor  | Shortcut
//
// | VS Code | kbd:[F2]
// |===
//
// image::https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif[]
pub(crate) fn rename(
    db: &RootDatabase,
    position: FilePosition,
    new_name: &str,
) -> RenameResult<SourceChange> {
    let sema = Semantics::new(db);
    let source_file = sema.parse(position.file_id);
    let syntax = source_file.syntax();

    let defs = find_definitions(&sema, syntax, position)?;

    let ops: RenameResult<Vec<SourceChange>> = defs
        .map(|(_namelike, def)| {
            if let Definition::Local(local) = def {
                if let Some(self_param) = local.as_self_param(sema.db) {
                    cov_mark::hit!(rename_self_to_param);
                    return rename_self_to_param(&sema, local, self_param, new_name);
                }
                if new_name == "self" {
                    cov_mark::hit!(rename_to_self);
                    return rename_to_self(&sema, local);
                }
            }
            def.rename(&sema, new_name)
        })
        .collect();

    ops?.into_iter()
        .reduce(|acc, elem| acc.merge(elem))
        .ok_or_else(|| format_err!("No references found at position"))
}

/// Called by the client when it is about to rename a file.
pub(crate) fn will_rename_file(
    db: &RootDatabase,
    file_id: FileId,
    new_name_stem: &str,
) -> Option<SourceChange> {
    let sema = Semantics::new(db);
    let module = sema.to_module_def(file_id)?;
    let def = Definition::Module(module);
    let mut change = def.rename(&sema, new_name_stem).ok()?;
    change.file_system_edits.clear();
    Some(change)
}

fn find_definitions(
    sema: &Semantics<RootDatabase>,
    syntax: &SyntaxNode,
    position: FilePosition,
) -> RenameResult<impl Iterator<Item = (ast::NameLike, Definition)>> {
    let symbols = sema
        .find_nodes_at_offset_with_descend::<ast::NameLike>(syntax, position.offset)
        .map(|name_like| {
            let res = match &name_like {
                // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet
                ast::NameLike::Name(name)
                    if name
                        .syntax()
                        .parent()
                        .map_or(false, |it| ast::Rename::can_cast(it.kind())) =>
                {
                    bail!("Renaming aliases is currently unsupported")
                }
                ast::NameLike::Name(name) => NameClass::classify(sema, name)
                    .map(|class| match class {
                        NameClass::Definition(it) | NameClass::ConstReference(it) => it,
                        NameClass::PatFieldShorthand { local_def, field_ref: _ } => {
                            Definition::Local(local_def)
                        }
                    })
                    .map(|def| (name_like.clone(), def))
                    .ok_or_else(|| format_err!("No references found at position")),
                ast::NameLike::NameRef(name_ref) => {
                    NameRefClass::classify(sema, name_ref)
                        .map(|class| match class {
                            NameRefClass::Definition(def) => def,
                            NameRefClass::FieldShorthand { local_ref, field_ref: _ } => {
                                Definition::Local(local_ref)
                            }
                        })
                        .ok_or_else(|| format_err!("No references found at position"))
                        .and_then(|def| {
                            // if the name differs from the definitions name it has to be an alias
                            if def
                                .name(sema.db)
                                .map_or(false, |it| it.to_smol_str() != name_ref.text().as_str())
                            {
                                Err(format_err!("Renaming aliases is currently unsupported"))
                            } else {
                                Ok((name_like.clone(), def))
                            }
                        })
                }
                ast::NameLike::Lifetime(lifetime) => {
                    NameRefClass::classify_lifetime(sema, lifetime)
                        .and_then(|class| match class {
                            NameRefClass::Definition(def) => Some(def),
                            _ => None,
                        })
                        .or_else(|| {
                            NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {
                                NameClass::Definition(it) => Some(it),
                                _ => None,
                            })
                        })
                        .map(|def| (name_like, def))
                        .ok_or_else(|| format_err!("No references found at position"))
                }
            };
            res
        });

    let res: RenameResult<Vec<_>> = symbols.collect();
    match res {
        Ok(v) => {
            if v.is_empty() {
                // FIXME: some semantic duplication between "empty vec" and "Err()"
                Err(format_err!("No references found at position"))
            } else {
                // remove duplicates, comparing `Definition`s
                Ok(v.into_iter().unique_by(|t| t.1))
            }
        }
        Err(e) => Err(e),
    }
}

fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
    if never!(local.is_self(sema.db)) {
        bail!("rename_to_self invoked on self");
    }

    let fn_def = match local.parent(sema.db) {
        hir::DefWithBody::Function(func) => func,
        _ => bail!("Cannot rename local to self outside of function"),
    };

    if fn_def.self_param(sema.db).is_some() {
        bail!("Method already has a self parameter");
    }

    let params = fn_def.assoc_fn_params(sema.db);
    let first_param = params
        .first()
        .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?;
    match first_param.as_local(sema.db) {
        Some(plocal) => {
            if plocal != local {
                bail!("Only the first parameter may be renamed to self");
            }
        }
        None => bail!("rename_to_self invoked on destructuring parameter"),
    }

    let assoc_item = fn_def
        .as_assoc_item(sema.db)
        .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?;
    let impl_ = match assoc_item.container(sema.db) {
        hir::AssocItemContainer::Trait(_) => {
            bail!("Cannot rename parameter to self for trait functions");
        }
        hir::AssocItemContainer::Impl(impl_) => impl_,
    };
    let first_param_ty = first_param.ty();
    let impl_ty = impl_.self_ty(sema.db);
    let (ty, self_param) = if impl_ty.remove_ref().is_some() {
        // if the impl is a ref to the type we can just match the `&T` with self directly
        (first_param_ty.clone(), "self")
    } else {
        first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| {
            (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })
        })
    };

    if ty != impl_ty {
        bail!("Parameter type differs from impl block type");
    }

    let InFile { file_id, value: param_source } =
        first_param.source(sema.db).ok_or_else(|| format_err!("No source for parameter found"))?;

    let def = Definition::Local(local);
    let usages = def.usages(sema).all();
    let mut source_change = SourceChange::default();
    source_change.extend(usages.iter().map(|(&file_id, references)| {
        (file_id, source_edit_from_references(references, def, "self"))
    }));
    source_change.insert_source_edit(
        file_id.original_file(sema.db),
        TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),
    );
    Ok(source_change)
}

fn rename_self_to_param(
    sema: &Semantics<RootDatabase>,
    local: hir::Local,
    self_param: hir::SelfParam,
    new_name: &str,
) -> RenameResult<SourceChange> {
    if new_name == "self" {
        // Let's do nothing rather than complain.
        cov_mark::hit!(rename_self_to_self);
        return Ok(SourceChange::default());
    }

    let identifier_kind = IdentifierKind::classify(new_name)?;

    let InFile { file_id, value: self_param } =
        self_param.source(sema.db).ok_or_else(|| format_err!("cannot find function source"))?;

    let def = Definition::Local(local);
    let usages = def.usages(sema).all();
    let edit = text_edit_from_self_param(&self_param, new_name)
        .ok_or_else(|| format_err!("No target type found"))?;
    if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {
        bail!("Cannot rename reference to `_` as it is being referenced multiple times");
    }
    let mut source_change = SourceChange::default();
    source_change.insert_source_edit(file_id.original_file(sema.db), edit);
    source_change.extend(usages.iter().map(|(&file_id, references)| {
        (file_id, source_edit_from_references(references, def, new_name))
    }));
    Ok(source_change)
}

fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
    fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
        if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
            return Some(p.path()?.segment()?.name_ref()?.text().to_string());
        }
        None
    }

    let impl_def = self_param.syntax().ancestors().find_map(ast::Impl::cast)?;
    let type_name = target_type_name(&impl_def)?;

    let mut replacement_text = String::from(new_name);
    replacement_text.push_str(": ");
    match (self_param.amp_token(), self_param.mut_token()) {
        (Some(_), None) => replacement_text.push('&'),
        (Some(_), Some(_)) => replacement_text.push_str("&mut "),
        (_, _) => (),
    };
    replacement_text.push_str(type_name.as_str());

    Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
}

#[cfg(test)]
mod tests {
    use expect_test::{expect, Expect};
    use stdx::trim_indent;
    use test_utils::assert_eq_text;
    use text_edit::TextEdit;

    use crate::{fixture, FileId};

    use super::{RangeInfo, RenameError};

    #[track_caller]
    fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
        let ra_fixture_after = &trim_indent(ra_fixture_after);
        let (analysis, position) = fixture::position(ra_fixture_before);
        let rename_result = analysis
            .rename(position, new_name)
            .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err));
        match rename_result {
            Ok(source_change) => {
                let mut text_edit_builder = TextEdit::builder();
                let mut file_id: Option<FileId> = None;
                for edit in source_change.source_file_edits {
                    file_id = Some(edit.0);
                    for indel in edit.1.into_iter() {
                        text_edit_builder.replace(indel.delete, indel.insert);
                    }
                }
                if let Some(file_id) = file_id {
                    let mut result = analysis.file_text(file_id).unwrap().to_string();
                    text_edit_builder.finish().apply(&mut result);
                    assert_eq_text!(ra_fixture_after, &*result);
                }
            }
            Err(err) => {
                if ra_fixture_after.starts_with("error:") {
                    let error_message = ra_fixture_after
                        .chars()
                        .into_iter()
                        .skip("error:".len())
                        .collect::<String>();
                    assert_eq!(error_message.trim(), err.to_string());
                } else {
                    panic!("Rename to '{}' failed unexpectedly: {}", new_name, err)
                }
            }
        };
    }

    fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
        let (analysis, position) = fixture::position(ra_fixture);
        let source_change =
            analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError");
        expect.assert_debug_eq(&source_change)
    }

    fn check_prepare(ra_fixture: &str, expect: Expect) {
        let (analysis, position) = fixture::position(ra_fixture);
        let result = analysis
            .prepare_rename(position)
            .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {}", err));
        match result {
            Ok(RangeInfo { range, info: () }) => {
                let source = analysis.file_text(position.file_id).unwrap();
                expect.assert_eq(&format!("{:?}: {}", range, &source[range]))
            }
            Err(RenameError(err)) => expect.assert_eq(&err),
        };
    }

    #[test]
    fn test_prepare_rename_namelikes() {
        check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);
        check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]);
        check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);
    }

    #[test]
    fn test_prepare_rename_in_macro() {
        check_prepare(
            r"macro_rules! foo {
    ($ident:ident) => {
        pub struct $ident;
    }
}
foo!(Foo$0);",
            expect![[r#"83..86: Foo"#]],
        );
    }

    #[test]
    fn test_prepare_rename_keyword() {
        check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);
    }

    #[test]
    fn test_prepare_rename_tuple_field() {
        check_prepare(
            r#"
struct Foo(i32);

fn baz() {
    let mut x = Foo(4);
    x.0$0 = 5;
}
"#,
            expect![[r#"No references found at position"#]],
        );
    }

    #[test]
    fn test_prepare_rename_builtin() {
        check_prepare(
            r#"
fn foo() {
    let x: i32$0 = 0;
}
"#,
            expect![[r#"No references found at position"#]],
        );
    }

    #[test]
    fn test_prepare_rename_self() {
        check_prepare(
            r#"
struct Foo {}

impl Foo {
    fn foo(self) -> Self$0 {
        self
    }
}
"#,
            expect![[r#"No references found at position"#]],
        );
    }

    #[test]
    fn test_rename_to_underscore() {
        check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);
    }

    #[test]
    fn test_rename_to_raw_identifier() {
        check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);
    }

    #[test]
    fn test_rename_to_invalid_identifier1() {
        check(
            "invalid!",
            r#"fn main() { let i$0 = 1; }"#,
            "error: Invalid name `invalid!`: not an identifier",
        );
    }

    #[test]
    fn test_rename_to_invalid_identifier2() {
        check(
            "multiple tokens",
            r#"fn main() { let i$0 = 1; }"#,
            "error: Invalid name `multiple tokens`: not an identifier",
        );
    }

    #[test]
    fn test_rename_to_invalid_identifier3() {
        check(
            "let",
            r#"fn main() { let i$0 = 1; }"#,
            "error: Invalid name `let`: not an identifier",
        );
    }

    #[test]
    fn test_rename_to_invalid_identifier_lifetime() {
        cov_mark::check!(rename_not_an_ident_ref);
        check(
            "'foo",
            r#"fn main() { let i$0 = 1; }"#,
            "error: Invalid name `'foo`: not an identifier",
        );
    }

    #[test]
    fn test_rename_to_invalid_identifier_lifetime2() {
        cov_mark::check!(rename_not_a_lifetime_ident_ref);
        check(
            "foo",
            r#"fn main<'a>(_: &'a$0 ()) {}"#,
            "error: Invalid name `foo`: not a lifetime identifier",
        );
    }

    #[test]
    fn test_rename_to_underscore_invalid() {
        cov_mark::check!(rename_underscore_multiple);
        check(
            "_",
            r#"fn main(foo$0: ()) {foo;}"#,
            "error: Cannot rename reference to `_` as it is being referenced multiple times",
        );
    }

    #[test]
    fn test_rename_mod_invalid() {
        check(
            "'foo",
            r#"mod foo$0 {}"#,
            "error: Invalid name `'foo`: cannot rename module to 'foo",
        );
    }

    #[test]
    fn test_rename_for_local() {
        check(
            "k",
            r#"
fn main() {
    let mut i = 1;
    let j = 1;
    i = i$0 + j;

    { i = 0; }

    i = 5;
}
"#,
            r#"
fn main() {
    let mut k = 1;
    let j = 1;
    k = k + j;

    { k = 0; }

    k = 5;
}
"#,
        );
    }

    #[test]
    fn test_rename_unresolved_reference() {
        check(
            "new_name",
            r#"fn main() { let _ = unresolved_ref$0; }"#,
            "error: No references found at position",
        );
    }

    #[test]
    fn test_rename_macro_multiple_occurrences() {
        check(
            "Baaah",
            r#"macro_rules! foo {
    ($ident:ident) => {
        const $ident: () = ();
        struct $ident {}
    };
}

foo!($0Foo);
const _: () = Foo;
const _: Foo = Foo {};
    "#,
            r#"
macro_rules! foo {
    ($ident:ident) => {
        const $ident: () = ();
        struct $ident {}
    };
}

foo!(Baaah);
const _: () = Baaah;
const _: Baaah = Baaah {};
    "#,
        )
    }

    #[test]
    fn test_rename_for_macro_args() {
        check(
            "b",
            r#"
macro_rules! foo {($i:ident) => {$i} }
fn main() {
    let a$0 = "test";
    foo!(a);
}
"#,
            r#"
macro_rules! foo {($i:ident) => {$i} }
fn main() {
    let b = "test";
    foo!(b);
}
"#,
        );
    }

    #[test]
    fn test_rename_for_macro_args_rev() {
        check(
            "b",
            r#"
macro_rules! foo {($i:ident) => {$i} }
fn main() {
    let a = "test";
    foo!(a$0);
}
"#,
            r#"
macro_rules! foo {($i:ident) => {$i} }
fn main() {
    let b = "test";
    foo!(b);
}
"#,
        );
    }

    #[test]
    fn test_rename_for_macro_define_fn() {
        check(
            "bar",
            r#"
macro_rules! define_fn {($id:ident) => { fn $id{} }}
define_fn!(foo);
fn main() {
    fo$0o();
}
"#,
            r#"
macro_rules! define_fn {($id:ident) => { fn $id{} }}
define_fn!(bar);
fn main() {
    bar();
}
"#,
        );
    }

    #[test]
    fn test_rename_for_macro_define_fn_rev() {
        check(
            "bar",
            r#"
macro_rules! define_fn {($id:ident) => { fn $id{} }}
define_fn!(fo$0o);
fn main() {
    foo();
}
"#,
            r#"
macro_rules! define_fn {($id:ident) => { fn $id{} }}
define_fn!(bar);
fn main() {
    bar();
}
"#,
        );
    }

    #[test]
    fn test_rename_for_param_inside() {
        check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);
    }

    #[test]
    fn test_rename_refs_for_fn_param() {
        check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);
    }

    #[test]
    fn test_rename_for_mut_param() {
        check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);
    }

    #[test]
    fn test_rename_struct_field() {
        check(
            "foo",
            r#"
struct Foo { field$0: i32 }

impl Foo {
    fn new(i: i32) -> Self {
        Self { field: i }
    }
}
"#,
            r#"
struct Foo { foo: i32 }

impl Foo {
    fn new(i: i32) -> Self {
        Self { foo: i }
    }
}
"#,
        );
    }

    #[test]
    fn test_rename_field_in_field_shorthand() {
        cov_mark::check!(test_rename_field_in_field_shorthand);
        check(
            "field",
            r#"
struct Foo { foo$0: i32 }

impl Foo {
    fn new(foo: i32) -> Self {
        Self { foo }
    }
}
"#,
            r#"
struct Foo { field: i32 }

impl Foo {
    fn new(foo: i32) -> Self {
        Self { field: foo }
    }
}
"#,
        );
    }

    #[test]
    fn test_rename_local_in_field_shorthand() {
        cov_mark::check!(test_rename_local_in_field_shorthand);
        check(
            "j",
            r#"
struct Foo { i: i32 }

impl Foo {
    fn new(i$0: i32) -> Self {
        Self { i }
    }
}
"#,
            r#"
struct Foo { i: i32 }

impl Foo {
    fn new(j: i32) -> Self {
        Self { i: j }
    }
}
"#,
        );
    }

    #[test]
    fn test_field_shorthand_correct_struct() {
        check(
            "j",
            r#"
struct Foo { i$0: i32 }
struct Bar { i: i32 }

impl Bar {
    fn new(i: i32) -> Self {
        Self { i }
    }
}
"#,
            r#"
struct Foo { j: i32 }
struct Bar { i: i32 }

impl Bar {
    fn new(i: i32) -> Self {
        Self { i }
    }
}
"#,
        );
    }

    #[test]
    fn test_shadow_local_for_struct_shorthand() {
        check(
            "j",
            r#"
struct Foo { i: i32 }

fn baz(i$0: i32) -> Self {
     let x = Foo { i };
     {
         let i = 0;
         Foo { i }
     }
}
"#,
            r#"
struct Foo { i: i32 }

fn baz(j: i32) -> Self {
     let x = Foo { i: j };
     {
         let i = 0;
         Foo { i }
     }
}
"#,
        );
    }

    #[test]
    fn test_rename_mod() {
        check_expect(
            "foo2",
            r#"
//- /lib.rs
mod bar;

//- /bar.rs
mod foo$0;

//- /bar/foo.rs
// empty
"#,
            expect![[r#"
                SourceChange {
                    source_file_edits: {
                        FileId(
                            1,
                        ): TextEdit {
                            indels: [
                                Indel {
                                    insert: "foo2",
                                    delete: 4..7,
                                },
                            ],
                        },
                    },
                    file_system_edits: [
                        MoveFile {
                            src: FileId(
                                2,
                            ),
                            dst: AnchoredPathBuf {
                                anchor: FileId(
                                    2,
                                ),
                                path: "foo2.rs",
                            },
                        },
                    ],
                    is_snippet: false,
                }
            "#]],
        );
    }

    #[test]
    fn test_rename_mod_in_use_tree() {
        check_expect(
            "quux",
            r#"
//- /main.rs
pub mod foo;
pub mod bar;
fn main() {}

//- /foo.rs
pub struct FooContent;

//- /bar.rs
use crate::foo$0::FooContent;
"#,
            expect![[r#"
                SourceChange {
                    source_file_edits: {
                        FileId(
                            0,
                        ): TextEdit {
                            indels: [
                                Indel {
                                    insert: "quux",
                                    delete: 8..11,
                                },
                            ],
                        },
                        FileId(
                            2,
                        ): TextEdit {
                            indels: [
                                Indel {
                                    insert: "quux",
                                    delete: 11..14,
                                },
                            ],
                        },
                    },
                    file_system_edits: [
                        MoveFile {
                            src: FileId(
                                1,
                            ),
                            dst: AnchoredPathBuf {
                                anchor: FileId(
                                    1,
                                ),
                                path: "quux.rs",
                            },
                        },
                    ],
                    is_snippet: false,
                }
            "#]],
        );
    }

    #[test]
    fn test_rename_mod_in_dir() {
        check_expect(
            "foo2",
            r#"
//- /lib.rs
mod fo$0o;
//- /foo/mod.rs
// empty
"#,
            expect![[r#"
                SourceChange {
                    source_file_edits: {
                        FileId(
                            0,
                        ): TextEdit {
                            indels: [
                                Indel {
                                    insert: "foo2",
                                    delete: 4..7,
                                },
                            ],
                        },
                    },
                    file_system_edits: [
                        MoveDir {
                            src: AnchoredPathBuf {
                                anchor: FileId(
                                    1,
                                ),
                                path: "foo",
                            },
                            src_id: FileId(
                                1,
                            ),
                            dst: AnchoredPathBuf {
                                anchor: FileId(
                                    1,
                                ),
                                path: "foo2",
                            },
                        },
                    ],
                    is_snippet: false,
                }
            "#]],
        );
    }

    #[test]
    fn test_rename_unusually_nested_mod() {
        check_expect(
            "bar",
            r#"
//- /lib.rs
mod outer { mod fo$0o; }

//- /outer/foo.rs
// empty
"#,
            expect![[r#"
                SourceChange {
                    source_file_edits: {
                        FileId(
                            0,
                        ): TextEdit {
                            indels: [
                                Indel {
                                    insert: "bar",
                                    delete: 16..19,
                                },
                            ],
                        },
                    },
                    file_system_edits: [
                        MoveFile {
                            src: FileId(
                                1,
                            ),
                            dst: AnchoredPathBuf {
                                anchor: FileId(
                                    1,
                                ),
                                path: "bar.rs",
                            },
                        },
                    ],
                    is_snippet: false,
                }
            "#]],
        );
    }

    #[test]
    fn test_module_rename_in_path() {
        check(
            "baz",
            r#"
mod $0foo {
    pub use self::bar as qux;
    pub fn bar() {}
}

fn main() { foo::bar(); }
"#,
            r#"
mod baz {
    pub use self::bar as qux;
    pub fn bar() {}
}

fn main() { baz::bar(); }
"#,
        );
    }

    #[test]
    fn test_rename_mod_filename_and_path() {
        check_expect(
            "foo2",
            r#"
//- /lib.rs
mod bar;
fn f() {
    bar::foo::fun()
}

//- /bar.rs
pub mod foo$0;

//- /bar/foo.rs
// pub fn fun() {}
"#,
            expect![[r#"
                SourceChange {
                    source_file_edits: {
                        FileId(
                            0,
                        ): TextEdit {
                            indels: [
                                Indel {
                                    insert: "foo2",
                                    delete: 27..30,
                                },
                            ],
                        },
                        FileId(
                            1,
                        ): TextEdit {
                            indels: [
                                Indel {
                                    insert: "foo2",
                                    delete: 8..11,
                                },
                            ],
                        },
                    },
                    file_system_edits: [
                        MoveFile {
                            src: FileId(
                                2,
                            ),
                            dst: AnchoredPathBuf {
                                anchor: FileId(
                                    2,
                                ),
                                path: "foo2.rs",
                            },
                        },
                    ],
                    is_snippet: false,
                }
            "#]],
        );
    }

    #[test]
    fn test_rename_mod_recursive() {
        check_expect(
            "foo2",
            r#"
//- /lib.rs
mod foo$0;

//- /foo.rs
mod bar;
mod corge;

//- /foo/bar.rs
mod qux;

//- /foo/bar/qux.rs
mod quux;

//- /foo/bar/qux/quux/mod.rs
// empty

//- /foo/corge.rs
// empty
"#,
            expect![[r#"
                SourceChange {
                    source_file_edits: {
                        FileId(
                            0,
                        ): TextEdit {
                            indels: [
                                Indel {
                                    insert: "foo2",
                                    delete: 4..7,
                                },
                            ],
                        },
                    },
                    file_system_edits: [
                        MoveDir {
                            src: AnchoredPathBuf {
                                anchor: FileId(
                                    1,
                                ),
                                path: "foo",
                            },
                            src_id: FileId(
                                1,
                            ),
                            dst: AnchoredPathBuf {
                                anchor: FileId(
                                    1,
                                ),
                                path: "foo2",
                            },
                        },
                    ],
                    is_snippet: false,
                }
            "#]],
        )
    }
    #[test]
    fn test_rename_mod_ref_by_super() {
        check(
            "baz",
            r#"
        mod $0foo {
        struct X;

        mod bar {
            use super::X;
        }
    }
            "#,
            r#"
        mod baz {
        struct X;

        mod bar {
            use super::X;
        }
    }
            "#,
        )
    }

    #[test]
    fn test_rename_mod_in_macro() {
        check(
            "bar",
            r#"
//- /foo.rs

//- /lib.rs
macro_rules! submodule {
    ($name:ident) => {
        mod $name;
    };
}

submodule!($0foo);
"#,
            r#"
macro_rules! submodule {
    ($name:ident) => {
        mod $name;
    };
}

submodule!(bar);
"#,
        )
    }

    #[test]
    fn test_enum_variant_from_module_1() {
        cov_mark::check!(rename_non_local);
        check(
            "Baz",
            r#"
mod foo {
    pub enum Foo { Bar$0 }
}

fn func(f: foo::Foo) {
    match f {
        foo::Foo::Bar => {}
    }
}
"#,
            r#"
mod foo {
    pub enum Foo { Baz }
}

fn func(f: foo::Foo) {
    match f {
        foo::Foo::Baz => {}
    }
}
"#,
        );
    }

    #[test]
    fn test_enum_variant_from_module_2() {
        check(
            "baz",
            r#"
mod foo {
    pub struct Foo { pub bar$0: uint }
}

fn foo(f: foo::Foo) {
    let _ = f.bar;
}
"#,
            r#"
mod foo {
    pub struct Foo { pub baz: uint }
}

fn foo(f: foo::Foo) {
    let _ = f.baz;
}
"#,
        );
    }

    #[test]
    fn test_parameter_to_self() {
        cov_mark::check!(rename_to_self);
        check(
            "self",
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(foo$0: &mut Foo) -> i32 {
        foo.i
    }
}
"#,
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(&mut self) -> i32 {
        self.i
    }
}
"#,
        );
        check(
            "self",
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(foo$0: Foo) -> i32 {
        foo.i
    }
}
"#,
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(self) -> i32 {
        self.i
    }
}
"#,
        );
    }

    #[test]
    fn test_parameter_to_self_error_no_impl() {
        check(
            "self",
            r#"
struct Foo { i: i32 }

fn f(foo$0: &mut Foo) -> i32 {
    foo.i
}
"#,
            "error: Cannot rename parameter to self for free function",
        );
        check(
            "self",
            r#"
struct Foo { i: i32 }
struct Bar;

impl Bar {
    fn f(foo$0: &mut Foo) -> i32 {
        foo.i
    }
}
"#,
            "error: Parameter type differs from impl block type",
        );
    }

    #[test]
    fn test_parameter_to_self_error_not_first() {
        check(
            "self",
            r#"
struct Foo { i: i32 }
impl Foo {
    fn f(x: (), foo$0: &mut Foo) -> i32 {
        foo.i
    }
}
"#,
            "error: Only the first parameter may be renamed to self",
        );
    }

    #[test]
    fn test_parameter_to_self_impl_ref() {
        check(
            "self",
            r#"
struct Foo { i: i32 }
impl &Foo {
    fn f(foo$0: &Foo) -> i32 {
        foo.i
    }
}
"#,
            r#"
struct Foo { i: i32 }
impl &Foo {
    fn f(self) -> i32 {
        self.i
    }
}
"#,
        );
    }

    #[test]
    fn test_self_to_parameter() {
        check(
            "foo",
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(&mut $0self) -> i32 {
        self.i
    }
}
"#,
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(foo: &mut Foo) -> i32 {
        foo.i
    }
}
"#,
        );
    }

    #[test]
    fn test_owned_self_to_parameter() {
        cov_mark::check!(rename_self_to_param);
        check(
            "foo",
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f($0self) -> i32 {
        self.i
    }
}
"#,
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(foo: Foo) -> i32 {
        foo.i
    }
}
"#,
        );
    }

    #[test]
    fn test_self_in_path_to_parameter() {
        check(
            "foo",
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(&self) -> i32 {
        let self_var = 1;
        self$0.i
    }
}
"#,
            r#"
struct Foo { i: i32 }

impl Foo {
    fn f(foo: &Foo) -> i32 {
        let self_var = 1;
        foo.i
    }
}
"#,
        );
    }

    #[test]
    fn test_rename_field_put_init_shorthand() {
        cov_mark::check!(test_rename_field_put_init_shorthand);
        check(
            "bar",
            r#"
struct Foo { i$0: i32 }

fn foo(bar: i32) -> Foo {
    Foo { i: bar }
}
"#,
            r#"
struct Foo { bar: i32 }

fn foo(bar: i32) -> Foo {
    Foo { bar }
}
"#,
        );
    }

    #[test]
    fn test_rename_local_put_init_shorthand() {
        cov_mark::check!(test_rename_local_put_init_shorthand);
        check(
            "i",
            r#"
struct Foo { i: i32 }

fn foo(bar$0: i32) -> Foo {
    Foo { i: bar }
}
"#,
            r#"
struct Foo { i: i32 }

fn foo(i: i32) -> Foo {
    Foo { i }
}
"#,
        );
    }

    #[test]
    fn test_struct_field_pat_into_shorthand() {
        cov_mark::check!(test_rename_field_put_init_shorthand_pat);
        check(
            "baz",
            r#"
struct Foo { i$0: i32 }

fn foo(foo: Foo) {
    let Foo { i: ref baz @ qux } = foo;
    let _ = qux;
}
"#,
            r#"
struct Foo { baz: i32 }

fn foo(foo: Foo) {
    let Foo { baz: ref baz @ qux } = foo;
    let _ = qux;
}
"#,
        );
        check(
            "baz",
            r#"
struct Foo { i$0: i32 }

fn foo(foo: Foo) {
    let Foo { i: ref baz } = foo;
    let _ = qux;
}
"#,
            r#"
struct Foo { baz: i32 }

fn foo(foo: Foo) {
    let Foo { ref baz } = foo;
    let _ = qux;
}
"#,
        );
    }

    #[test]
    fn test_struct_local_pat_into_shorthand() {
        cov_mark::check!(test_rename_local_put_init_shorthand_pat);
        check(
            "field",
            r#"
struct Foo { field: i32 }

fn foo(foo: Foo) {
    let Foo { field: qux$0 } = foo;
    let _ = qux;
}
"#,
            r#"
struct Foo { field: i32 }

fn foo(foo: Foo) {
    let Foo { field } = foo;
    let _ = field;
}
"#,
        );
        check(
            "field",
            r#"
struct Foo { field: i32 }

fn foo(foo: Foo) {
    let Foo { field: x @ qux$0 } = foo;
    let _ = qux;
}
"#,
            r#"
struct Foo { field: i32 }

fn foo(foo: Foo) {
    let Foo { field: x @ field } = foo;
    let _ = field;
}
"#,
        );
    }

    #[test]
    fn test_rename_binding_in_destructure_pat() {
        let expected_fixture = r#"
struct Foo {
    i: i32,
}

fn foo(foo: Foo) {
    let Foo { i: bar } = foo;
    let _ = bar;
}
"#;
        check(
            "bar",
            r#"
struct Foo {
    i: i32,
}

fn foo(foo: Foo) {
    let Foo { i: b } = foo;
    let _ = b$0;
}
"#,
            expected_fixture,
        );
        check(
            "bar",
            r#"
struct Foo {
    i: i32,
}

fn foo(foo: Foo) {
    let Foo { i } = foo;
    let _ = i$0;
}
"#,
            expected_fixture,
        );
    }

    #[test]
    fn test_rename_binding_in_destructure_param_pat() {
        check(
            "bar",
            r#"
struct Foo {
    i: i32
}

fn foo(Foo { i }: Foo) -> i32 {
    i$0
}
"#,
            r#"
struct Foo {
    i: i32
}

fn foo(Foo { i: bar }: Foo) -> i32 {
    bar
}
"#,
        )
    }

    #[test]
    fn test_struct_field_complex_ident_pat() {
        cov_mark::check!(rename_record_pat_field_name_split);
        check(
            "baz",
            r#"
struct Foo { i$0: i32 }

fn foo(foo: Foo) {
    let Foo { ref i } = foo;
}
"#,
            r#"
struct Foo { baz: i32 }

fn foo(foo: Foo) {
    let Foo { baz: ref i } = foo;
}
"#,
        );
    }

    #[test]
    fn test_rename_lifetimes() {
        cov_mark::check!(rename_lifetime);
        check(
            "'yeeee",
            r#"
trait Foo<'a> {
    fn foo() -> &'a ();
}
impl<'a> Foo<'a> for &'a () {
    fn foo() -> &'a$0 () {
        unimplemented!()
    }
}
"#,
            r#"
trait Foo<'a> {
    fn foo() -> &'a ();
}
impl<'yeeee> Foo<'yeeee> for &'yeeee () {
    fn foo() -> &'yeeee () {
        unimplemented!()
    }
}
"#,
        )
    }

    #[test]
    fn test_rename_bind_pat() {
        check(
            "new_name",
            r#"
fn main() {
    enum CustomOption<T> {
        None,
        Some(T),
    }

    let test_variable = CustomOption::Some(22);

    match test_variable {
        CustomOption::Some(foo$0) if foo == 11 => {}
        _ => (),
    }
}"#,
            r#"
fn main() {
    enum CustomOption<T> {
        None,
        Some(T),
    }

    let test_variable = CustomOption::Some(22);

    match test_variable {
        CustomOption::Some(new_name) if new_name == 11 => {}
        _ => (),
    }
}"#,
        );
    }

    #[test]
    fn test_rename_label() {
        check(
            "'foo",
            r#"
fn foo<'a>() -> &'a () {
    'a: {
        'b: loop {
            break 'a$0;
        }
    }
}
"#,
            r#"
fn foo<'a>() -> &'a () {
    'foo: {
        'b: loop {
            break 'foo;
        }
    }
}
"#,
        )
    }

    #[test]
    fn test_self_to_self() {
        cov_mark::check!(rename_self_to_self);
        check(
            "self",
            r#"
struct Foo;
impl Foo {
    fn foo(self$0) {}
}
"#,
            r#"
struct Foo;
impl Foo {
    fn foo(self) {}
}
"#,
        )
    }

    #[test]
    fn test_rename_field_in_pat_in_macro_doesnt_shorthand() {
        // ideally we would be able to make this emit a short hand, but I doubt this is easily possible
        check(
            "baz",
            r#"
macro_rules! foo {
    ($pattern:pat) => {
        let $pattern = loop {};
    };
}
struct Foo {
    bar$0: u32,
}
fn foo() {
    foo!(Foo { bar: baz });
}
"#,
            r#"
macro_rules! foo {
    ($pattern:pat) => {
        let $pattern = loop {};
    };
}
struct Foo {
    baz: u32,
}
fn foo() {
    foo!(Foo { baz: baz });
}
"#,
        )
    }

    #[test]
    fn test_rename_tuple_field() {
        check(
            "foo",
            r#"
struct Foo(i32);

fn baz() {
    let mut x = Foo(4);
    x.0$0 = 5;
}
"#,
            "error: No identifier available to rename",
        );
    }

    #[test]
    fn test_rename_builtin() {
        check(
            "foo",
            r#"
fn foo() {
    let x: i32$0 = 0;
}
"#,
            "error: Cannot rename builtin type",
        );
    }

    #[test]
    fn test_rename_self() {
        check(
            "foo",
            r#"
struct Foo {}

impl Foo {
    fn foo(self) -> Self$0 {
        self
    }
}
"#,
            "error: Cannot rename `Self`",
        );
    }

    #[test]
    fn test_rename_ignores_self_ty() {
        check(
            "Fo0",
            r#"
struct $0Foo;

impl Foo where Self: {}
"#,
            r#"
struct Fo0;

impl Fo0 where Self: {}
"#,
        );
    }

    #[test]
    fn test_rename_fails_on_aliases() {
        check(
            "Baz",
            r#"
struct Foo;
use Foo as Bar$0;
"#,
            "error: Renaming aliases is currently unsupported",
        );
        check(
            "Baz",
            r#"
struct Foo;
use Foo as Bar;
use Bar$0;
"#,
            "error: Renaming aliases is currently unsupported",
        );
    }

    #[test]
    fn test_rename_trait_method() {
        let res = r"
trait Foo {
    fn foo(&self) {
        self.foo();
    }
}

impl Foo for () {
    fn foo(&self) {
        self.foo();
    }
}";
        check(
            "foo",
            r#"
trait Foo {
    fn bar$0(&self) {
        self.bar();
    }
}

impl Foo for () {
    fn bar(&self) {
        self.bar();
    }
}"#,
            res,
        );
        check(
            "foo",
            r#"
trait Foo {
    fn bar(&self) {
        self.bar$0();
    }
}

impl Foo for () {
    fn bar(&self) {
        self.bar();
    }
}"#,
            res,
        );
        check(
            "foo",
            r#"
trait Foo {
    fn bar(&self) {
        self.bar();
    }
}

impl Foo for () {
    fn bar$0(&self) {
        self.bar();
    }
}"#,
            res,
        );
        check(
            "foo",
            r#"
trait Foo {
    fn bar(&self) {
        self.bar();
    }
}

impl Foo for () {
    fn bar(&self) {
        self.bar$0();
    }
}"#,
            res,
        );
    }

    #[test]
    fn test_rename_trait_method_prefix_of_second() {
        check(
            "qux",
            r#"
trait Foo {
    fn foo$0() {}
    fn foobar() {}
}
"#,
            r#"
trait Foo {
    fn qux() {}
    fn foobar() {}
}
"#,
        );
    }

    #[test]
    fn test_rename_trait_const() {
        let res = r"
trait Foo {
    const FOO: ();
}

impl Foo for () {
    const FOO: ();
}
fn f() { <()>::FOO; }";
        check(
            "FOO",
            r#"
trait Foo {
    const BAR$0: ();
}

impl Foo for () {
    const BAR: ();
}
fn f() { <()>::BAR; }"#,
            res,
        );
        check(
            "FOO",
            r#"
trait Foo {
    const BAR: ();
}

impl Foo for () {
    const BAR$0: ();
}
fn f() { <()>::BAR; }"#,
            res,
        );
        check(
            "FOO",
            r#"
trait Foo {
    const BAR: ();
}

impl Foo for () {
    const BAR: ();
}
fn f() { <()>::BAR$0; }"#,
            res,
        );
    }

    #[test]
    fn defs_from_macros_arent_renamed() {
        check(
            "lol",
            r#"
macro_rules! m { () => { fn f() {} } }
m!();
fn main() { f$0()  }
"#,
            "error: No identifier available to rename",
        )
    }

    #[test]
    fn attributed_item() {
        check(
            "function",
            r#"
//- proc_macros: identity

#[proc_macros::identity]
fn func$0() {
    func();
}
"#,
            r#"

#[proc_macros::identity]
fn function() {
    function();
}
"#,
        )
    }

    #[test]
    fn in_macro_multi_mapping() {
        check(
            "a",
            r#"
fn foo() {
    macro_rules! match_ast2 {
        ($node:ident {
            $( $res:expr, )*
        }) => {{
            $( if $node { $res } else )*
            { loop {} }
        }};
    }
    let $0d = 3;
    match_ast2! {
        d {
            d,
            d,
        }
    };
}
"#,
            r#"
fn foo() {
    macro_rules! match_ast2 {
        ($node:ident {
            $( $res:expr, )*
        }) => {{
            $( if $node { $res } else )*
            { loop {} }
        }};
    }
    let a = 3;
    match_ast2! {
        a {
            a,
            a,
        }
    };
}
"#,
        )
    }

    #[test]
    fn rename_multi_local() {
        check(
            "bar",
            r#"
fn foo((foo$0 | foo | foo): ()) {
    foo;
    let foo;
}
"#,
            r#"
fn foo((bar | bar | bar): ()) {
    bar;
    let foo;
}
"#,
        );
        check(
            "bar",
            r#"
fn foo((foo | foo$0 | foo): ()) {
    foo;
    let foo;
}
"#,
            r#"
fn foo((bar | bar | bar): ()) {
    bar;
    let foo;
}
"#,
        );
        check(
            "bar",
            r#"
fn foo((foo | foo | foo): ()) {
    foo$0;
    let foo;
}
"#,
            r#"
fn foo((bar | bar | bar): ()) {
    bar;
    let foo;
}
"#,
        );
    }
}
