//! Completion tests for attributes.
use expect_test::{expect, Expect};

use crate::tests::{check_edit, completion_list};

fn check(ra_fixture: &str, expect: Expect) {
    let actual = completion_list(ra_fixture);
    expect.assert_eq(&actual);
}

#[test]
fn proc_macros() {
    check(
        r#"
//- proc_macros: identity
#[$0]
struct Foo;
"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at derive(…)
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at non_exhaustive
            at repr(…)
            at warn(…)
            md proc_macros
            kw crate::
            kw self::
        "#]],
    )
}

#[test]
fn proc_macros_on_comment() {
    check(
        r#"
//- proc_macros: identity
/// $0
#[proc_macros::identity]
struct Foo;
"#,
        expect![[r#""#]],
    )
}

#[test]
fn proc_macros_qualified() {
    check(
        r#"
//- proc_macros: identity
#[proc_macros::$0]
struct Foo;
"#,
        expect![[r#"
            at identity proc_macro identity
        "#]],
    )
}

#[test]
fn inside_nested_attr() {
    check(r#"#[cfg($0)]"#, expect![[]])
}

#[test]
fn with_existing_attr() {
    check(
        r#"#[no_mangle] #[$0] mcall!();"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at forbid(…)
            at warn(…)
            kw crate::
            kw self::
        "#]],
    )
}

#[test]
fn attr_on_source_file() {
    check(
        r#"#![$0]"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at crate_name = ""
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at feature(…)
            at forbid(…)
            at must_use
            at no_implicit_prelude
            at no_main
            at no_mangle
            at no_std
            at recursion_limit = "…"
            at type_length_limit = …
            at warn(…)
            at windows_subsystem = "…"
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_module() {
    check(
        r#"#[$0] mod foo;"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at macro_use
            at must_use
            at no_mangle
            at path = "…"
            at warn(…)
            kw crate::
            kw self::
            kw super::
        "#]],
    );
    check(
        r#"mod foo {#![$0]}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_implicit_prelude
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
            kw super::
        "#]],
    );
}

#[test]
fn attr_on_macro_rules() {
    check(
        r#"#[$0] macro_rules! foo {}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at macro_export
            at macro_use
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_macro_def() {
    check(
        r#"#[$0] macro foo {}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_extern_crate() {
    check(
        r#"#[$0] extern crate foo;"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at macro_use
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_use() {
    check(
        r#"#[$0] use foo;"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_type_alias() {
    check(
        r#"#[$0] type foo = ();"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_struct() {
    check(
        r#"
//- minicore:derive
#[$0]
struct Foo;
"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at derive           macro derive
            at derive(…)
            at derive_const     macro derive_const
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at non_exhaustive
            at repr(…)
            at warn(…)
            md core
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_enum() {
    check(
        r#"#[$0] enum Foo {}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at derive(…)
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at non_exhaustive
            at repr(…)
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_const() {
    check(
        r#"#[$0] const FOO: () = ();"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_static() {
    check(
        r#"#[$0] static FOO: () = ()"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at export_name = "…"
            at forbid(…)
            at global_allocator
            at link_name = "…"
            at link_section = "…"
            at must_use
            at no_mangle
            at used
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_trait() {
    check(
        r#"#[$0] trait Foo {}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_impl() {
    check(
        r#"#[$0] impl () {}"#,
        expect![[r#"
            at allow(…)
            at automatically_derived
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
    check(
        r#"impl () {#![$0]}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_extern_block() {
    check(
        r#"#[$0] extern {}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at link
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
    check(
        r#"extern {#![$0]}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at forbid(…)
            at link
            at must_use
            at no_mangle
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_variant() {
    check(
        r#"enum Foo { #[$0] Bar }"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at deny(…)
            at forbid(…)
            at non_exhaustive
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_on_fn() {
    check(
        r#"#[$0] fn main() {}"#,
        expect![[r#"
            at allow(…)
            at cfg(…)
            at cfg_attr(…)
            at cold
            at deny(…)
            at deprecated
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at export_name = "…"
            at forbid(…)
            at ignore = "…"
            at inline
            at link_name = "…"
            at link_section = "…"
            at must_use
            at must_use
            at no_mangle
            at panic_handler
            at proc_macro
            at proc_macro_attribute
            at proc_macro_derive(…)
            at should_panic
            at target_feature(enable = "…")
            at test
            at track_caller
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn attr_in_source_file_end() {
    check(
        r#"#[$0]"#,
        expect![[r#"
            at allow(…)
            at automatically_derived
            at cfg(…)
            at cfg_attr(…)
            at cold
            at deny(…)
            at deprecated
            at derive(…)
            at doc = "…"
            at doc(alias = "…")
            at doc(hidden)
            at export_name = "…"
            at forbid(…)
            at global_allocator
            at ignore = "…"
            at inline
            at link
            at link_name = "…"
            at link_section = "…"
            at macro_export
            at macro_use
            at must_use
            at no_mangle
            at non_exhaustive
            at panic_handler
            at path = "…"
            at proc_macro
            at proc_macro_attribute
            at proc_macro_derive(…)
            at repr(…)
            at should_panic
            at target_feature(enable = "…")
            at test
            at track_caller
            at used
            at warn(…)
            kw crate::
            kw self::
        "#]],
    );
}

#[test]
fn invalid_path() {
    check(
        r#"
//- proc_macros: identity
#[proc_macros:::$0]
struct Foo;
"#,
        expect![[r#""#]],
    );

    check(
        r#"
//- minicore: derive, copy
mod foo {
    pub use Copy as Bar;
}
#[derive(foo:::::$0)]
struct Foo;
"#,
        expect![""],
    );
}

mod cfg {
    use super::*;

    #[test]
    fn cfg_target_endian() {
        check(
            r#"#[cfg(target_endian = $0"#,
            expect![[r#"
                ba big
                ba little
            "#]],
        );
    }
}

mod derive {
    use super::*;

    fn check_derive(ra_fixture: &str, expect: Expect) {
        let actual = completion_list(ra_fixture);
        expect.assert_eq(&actual);
    }

    #[test]
    fn no_completion_for_incorrect_derive() {
        check_derive(
            r#"
//- minicore: derive, copy, clone, ord, eq, default, fmt
#[derive{$0)] struct Test;
"#,
            expect![[]],
        )
    }

    #[test]
    fn empty_derive() {
        check_derive(
            r#"
//- minicore: derive, copy, clone, ord, eq, default, fmt
#[derive($0)] struct Test;
"#,
            expect![[r#"
                de Clone                  macro Clone
                de Clone, Copy
                de Default                macro Default
                de PartialEq              macro PartialEq
                de PartialEq, Eq
                de PartialEq, Eq, PartialOrd, Ord
                de PartialEq, PartialOrd
                md core
                kw crate::
                kw self::
            "#]],
        );
    }

    #[test]
    fn derive_with_input_before() {
        check_derive(
            r#"
//- minicore: derive, copy, clone, ord, eq, default, fmt
#[derive(serde::Serialize, PartialEq, $0)] struct Test;
"#,
            expect![[r#"
                de Clone               macro Clone
                de Clone, Copy
                de Default             macro Default
                de Eq
                de Eq, PartialOrd, Ord
                de PartialOrd
                md core
                kw crate::
                kw self::
            "#]],
        )
    }

    #[test]
    fn derive_with_input_after() {
        check_derive(
            r#"
//- minicore: derive, copy, clone, ord, eq, default, fmt
#[derive($0 serde::Serialize, PartialEq)] struct Test;
"#,
            expect![[r#"
                de Clone               macro Clone
                de Clone, Copy
                de Default             macro Default
                de Eq
                de Eq, PartialOrd, Ord
                de PartialOrd
                md core
                kw crate::
                kw self::
            "#]],
        );
    }

    #[test]
    fn derive_with_existing_derives() {
        check_derive(
            r#"
//- minicore: derive, copy, clone, ord, eq, default, fmt
#[derive(PartialEq, Eq, Or$0)] struct Test;
"#,
            expect![[r#"
                de Clone           macro Clone
                de Clone, Copy
                de Default         macro Default
                de PartialOrd
                de PartialOrd, Ord
                md core
                kw crate::
                kw self::
            "#]],
        );
    }

    #[test]
    fn derive_flyimport() {
        check_derive(
            r#"
//- proc_macros: derive_identity
//- minicore: derive
#[derive(der$0)] struct Test;
"#,
            expect![[r#"
                de DeriveIdentity (use proc_macros::DeriveIdentity) proc_macro DeriveIdentity
                md core
                md proc_macros
                kw crate::
                kw self::
            "#]],
        );
        check_derive(
            r#"
//- proc_macros: derive_identity
//- minicore: derive
use proc_macros::DeriveIdentity;
#[derive(der$0)] struct Test;
"#,
            expect![[r#"
                de DeriveIdentity proc_macro DeriveIdentity
                md core
                md proc_macros
                kw crate::
                kw self::
            "#]],
        );
    }

    #[test]
    fn derive_flyimport_edit() {
        check_edit(
            "DeriveIdentity",
            r#"
//- proc_macros: derive_identity
//- minicore: derive
#[derive(der$0)] struct Test;
"#,
            r#"
use proc_macros::DeriveIdentity;

#[derive(DeriveIdentity)] struct Test;
"#,
        );
    }

    #[test]
    fn qualified() {
        check_derive(
            r#"
//- proc_macros: derive_identity
//- minicore: derive, copy, clone
#[derive(proc_macros::$0)] struct Test;
"#,
            expect![[r#"
                de DeriveIdentity proc_macro DeriveIdentity
            "#]],
        );
        check_derive(
            r#"
//- proc_macros: derive_identity
//- minicore: derive, copy, clone
#[derive(proc_macros::C$0)] struct Test;
"#,
            expect![[r#"
                de DeriveIdentity proc_macro DeriveIdentity
            "#]],
        );
    }
}

mod lint {
    use super::*;

    #[test]
    fn lint_empty() {
        check_edit(
            "deprecated",
            r#"#[allow($0)] struct Test;"#,
            r#"#[allow(deprecated)] struct Test;"#,
        )
    }

    #[test]
    fn lint_with_existing() {
        check_edit(
            "deprecated",
            r#"#[allow(keyword_idents, $0)] struct Test;"#,
            r#"#[allow(keyword_idents, deprecated)] struct Test;"#,
        )
    }

    #[test]
    fn lint_qualified() {
        check_edit(
            "deprecated",
            r#"#[allow(keyword_idents, $0)] struct Test;"#,
            r#"#[allow(keyword_idents, deprecated)] struct Test;"#,
        )
    }

    #[test]
    fn lint_feature() {
        check_edit(
            "box_patterns",
            r#"#[feature(box_$0)] struct Test;"#,
            r#"#[feature(box_patterns)] struct Test;"#,
        )
    }

    #[test]
    fn lint_clippy_unqualified() {
        check_edit(
            "clippy::as_conversions",
            r#"#[allow($0)] struct Test;"#,
            r#"#[allow(clippy::as_conversions)] struct Test;"#,
        );
    }

    #[test]
    fn lint_clippy_qualified() {
        check_edit(
            "as_conversions",
            r#"#[allow(clippy::$0)] struct Test;"#,
            r#"#[allow(clippy::as_conversions)] struct Test;"#,
        );
    }

    #[test]
    fn lint_rustdoc_unqualified() {
        check_edit(
            "rustdoc::bare_urls",
            r#"#[allow($0)] struct Test;"#,
            r#"#[allow(rustdoc::bare_urls)] struct Test;"#,
        );
    }

    #[test]
    fn lint_rustdoc_qualified() {
        check_edit(
            "bare_urls",
            r#"#[allow(rustdoc::$0)] struct Test;"#,
            r#"#[allow(rustdoc::bare_urls)] struct Test;"#,
        );
    }

    #[test]
    fn lint_unclosed() {
        check_edit(
            "deprecated",
            r#"#[allow(dep$0 struct Test;"#,
            r#"#[allow(deprecated struct Test;"#,
        );
        check_edit(
            "bare_urls",
            r#"#[allow(rustdoc::$0 struct Test;"#,
            r#"#[allow(rustdoc::bare_urls struct Test;"#,
        );
    }
}

mod repr {
    use super::*;

    fn check_repr(ra_fixture: &str, expect: Expect) {
        let actual = completion_list(ra_fixture);
        expect.assert_eq(&actual);
    }

    #[test]
    fn no_completion_for_incorrect_repr() {
        check_repr(r#"#[repr{$0)] struct Test;"#, expect![[]])
    }

    #[test]
    fn empty() {
        check_repr(
            r#"#[repr($0)] struct Test;"#,
            expect![[r#"
                ba C
                ba align($0)
                ba i16
                ba i28
                ba i32
                ba i64
                ba i8
                ba isize
                ba packed
                ba transparent
                ba u128
                ba u16
                ba u32
                ba u64
                ba u8
                ba usize
            "#]],
        );
    }

    #[test]
    fn transparent() {
        check_repr(r#"#[repr(transparent, $0)] struct Test;"#, expect![[r#""#]]);
    }

    #[test]
    fn align() {
        check_repr(
            r#"#[repr(align(1), $0)] struct Test;"#,
            expect![[r#"
                ba C
                ba i16
                ba i28
                ba i32
                ba i64
                ba i8
                ba isize
                ba transparent
                ba u128
                ba u16
                ba u32
                ba u64
                ba u8
                ba usize
            "#]],
        );
    }

    #[test]
    fn packed() {
        check_repr(
            r#"#[repr(packed, $0)] struct Test;"#,
            expect![[r#"
                ba C
                ba i16
                ba i28
                ba i32
                ba i64
                ba i8
                ba isize
                ba transparent
                ba u128
                ba u16
                ba u32
                ba u64
                ba u8
                ba usize
            "#]],
        );
    }

    #[test]
    fn c() {
        check_repr(
            r#"#[repr(C, $0)] struct Test;"#,
            expect![[r#"
                ba align($0)
                ba i16
                ba i28
                ba i32
                ba i64
                ba i8
                ba isize
                ba packed
                ba u128
                ba u16
                ba u32
                ba u64
                ba u8
                ba usize
            "#]],
        );
    }

    #[test]
    fn prim() {
        check_repr(
            r#"#[repr(usize, $0)] struct Test;"#,
            expect![[r#"
                ba C
                ba align($0)
                ba packed
            "#]],
        );
    }
}
