mod builtin_type_shadow;
mod literal_suffix;
mod mixed_case_hex_literals;
mod redundant_at_rest_pattern;
mod redundant_pattern;
mod unneeded_field_pattern;
mod unneeded_wildcard_pattern;
mod zero_prefixed_literal;

use clippy_utils::diagnostics::span_lint;
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{Expr, ExprKind, Generics, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind};
use rustc_ast::token;
use rustc_ast::visit::FnKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::declare_lint_pass;
use rustc_span::Span;

declare_clippy_lint! {
    /// ### What it does
    /// Checks for structure field patterns bound to wildcards.
    ///
    /// ### Why restrict this?
    /// Using `..` instead is shorter and leaves the focus on
    /// the fields that are actually bound.
    ///
    /// ### Example
    /// ```no_run
    /// # struct Foo {
    /// #     a: i32,
    /// #     b: i32,
    /// #     c: i32,
    /// # }
    /// let f = Foo { a: 0, b: 0, c: 0 };
    ///
    /// match f {
    ///     Foo { a: _, b: 0, .. } => {},
    ///     Foo { a: _, b: _, c: _ } => {},
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// # struct Foo {
    /// #     a: i32,
    /// #     b: i32,
    /// #     c: i32,
    /// # }
    /// let f = Foo { a: 0, b: 0, c: 0 };
    ///
    /// match f {
    ///     Foo { b: 0, .. } => {},
    ///     Foo { .. } => {},
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub UNNEEDED_FIELD_PATTERN,
    restriction,
    "struct fields bound to a wildcard instead of using `..`"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for function arguments having the similar names
    /// differing by an underscore.
    ///
    /// ### Why is this bad?
    /// It affects code readability.
    ///
    /// ### Example
    /// ```no_run
    /// fn foo(a: i32, _a: i32) {}
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// fn bar(a: i32, _b: i32) {}
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub DUPLICATE_UNDERSCORE_ARGUMENT,
    style,
    "function arguments having names which only differ by an underscore"
}

declare_clippy_lint! {
    /// ### What it does
    /// Warns on hexadecimal literals with mixed-case letter
    /// digits.
    ///
    /// ### Why is this bad?
    /// It looks confusing.
    ///
    /// ### Example
    /// ```no_run
    /// # let _ =
    /// 0x1a9BAcD
    /// # ;
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// # let _ =
    /// 0x1A9BACD
    /// # ;
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub MIXED_CASE_HEX_LITERALS,
    style,
    "hex literals whose letter digits are not consistently upper- or lowercased"
}

declare_clippy_lint! {
    /// ### What it does
    /// Warns if literal suffixes are not separated by an
    /// underscore.
    /// To enforce unseparated literal suffix style,
    /// see the `separated_literal_suffix` lint.
    ///
    /// ### Why restrict this?
    /// Suffix style should be consistent.
    ///
    /// ### Example
    /// ```no_run
    /// # let _ =
    /// 123832i32
    /// # ;
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// # let _ =
    /// 123832_i32
    /// # ;
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub UNSEPARATED_LITERAL_SUFFIX,
    restriction,
    "literals whose suffix is not separated by an underscore"
}

declare_clippy_lint! {
    /// ### What it does
    /// Warns if literal suffixes are separated by an underscore.
    /// To enforce separated literal suffix style,
    /// see the `unseparated_literal_suffix` lint.
    ///
    /// ### Why restrict this?
    /// Suffix style should be consistent.
    ///
    /// ### Example
    /// ```no_run
    /// # let _ =
    /// 123832_i32
    /// # ;
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// # let _ =
    /// 123832i32
    /// # ;
    /// ```
    #[clippy::version = "1.58.0"]
    pub SEPARATED_LITERAL_SUFFIX,
    restriction,
    "literals whose suffix is separated by an underscore"
}

declare_clippy_lint! {
    /// ### What it does
    /// Warns if an integral constant literal starts with `0`.
    ///
    /// ### Why is this bad?
    /// In some languages (including the infamous C language
    /// and most of its
    /// family), this marks an octal constant. In Rust however, this is a decimal
    /// constant. This could
    /// be confusing for both the writer and a reader of the constant.
    ///
    /// ### Example
    ///
    /// In Rust:
    /// ```no_run
    /// fn main() {
    ///     let a = 0123;
    ///     println!("{}", a);
    /// }
    /// ```
    ///
    /// prints `123`, while in C:
    ///
    /// ```c
    /// #include <stdio.h>
    ///
    /// int main() {
    ///     int a = 0123;
    ///     printf("%d\n", a);
    /// }
    /// ```
    ///
    /// prints `83` (as `83 == 0o123` while `123 == 0o173`).
    #[clippy::version = "pre 1.29.0"]
    pub ZERO_PREFIXED_LITERAL,
    complexity,
    "integer literals starting with `0`"
}

declare_clippy_lint! {
    /// ### What it does
    /// Warns if a generic shadows a built-in type.
    ///
    /// ### Why is this bad?
    /// This gives surprising type errors.
    ///
    /// ### Example
    ///
    /// ```ignore
    /// impl<u32> Foo<u32> {
    ///     fn impl_func(&self) -> u32 {
    ///         42
    ///     }
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub BUILTIN_TYPE_SHADOW,
    style,
    "shadowing a builtin type"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for patterns in the form `name @ _`.
    ///
    /// ### Why is this bad?
    /// It's almost always more readable to just use direct
    /// bindings.
    ///
    /// ### Example
    /// ```no_run
    /// # let v = Some("abc");
    /// match v {
    ///     Some(x) => (),
    ///     y @ _ => (),
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// # let v = Some("abc");
    /// match v {
    ///     Some(x) => (),
    ///     y => (),
    /// }
    /// ```
    #[clippy::version = "pre 1.29.0"]
    pub REDUNDANT_PATTERN,
    style,
    "using `name @ _` in a pattern"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for tuple patterns with a wildcard
    /// pattern (`_`) is next to a rest pattern (`..`).
    ///
    /// _NOTE_: While `_, ..` means there is at least one element left, `..`
    /// means there are 0 or more elements left. This can make a difference
    /// when refactoring, but shouldn't result in errors in the refactored code,
    /// since the wildcard pattern isn't used anyway.
    ///
    /// ### Why is this bad?
    /// The wildcard pattern is unneeded as the rest pattern
    /// can match that element as well.
    ///
    /// ### Example
    /// ```no_run
    /// # struct TupleStruct(u32, u32, u32);
    /// # let t = TupleStruct(1, 2, 3);
    /// match t {
    ///     TupleStruct(0, .., _) => (),
    ///     _ => (),
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```no_run
    /// # struct TupleStruct(u32, u32, u32);
    /// # let t = TupleStruct(1, 2, 3);
    /// match t {
    ///     TupleStruct(0, ..) => (),
    ///     _ => (),
    /// }
    /// ```
    #[clippy::version = "1.40.0"]
    pub UNNEEDED_WILDCARD_PATTERN,
    complexity,
    "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)"
}

declare_clippy_lint! {
    /// ### What it does
    /// Checks for `[all @ ..]` patterns.
    ///
    /// ### Why is this bad?
    /// In all cases, `all` works fine and can often make code simpler, as you possibly won't need
    /// to convert from say a `Vec` to a slice by dereferencing.
    ///
    /// ### Example
    /// ```rust,ignore
    /// if let [all @ ..] = &*v {
    ///     // NOTE: Type is a slice here
    ///     println!("all elements: {all:#?}");
    /// }
    /// ```
    /// Use instead:
    /// ```rust,ignore
    /// if let all = v {
    ///     // NOTE: Type is a `Vec` here
    ///     println!("all elements: {all:#?}");
    /// }
    /// // or
    /// println!("all elements: {v:#?}");
    /// ```
    #[clippy::version = "1.72.0"]
    pub REDUNDANT_AT_REST_PATTERN,
    complexity,
    "checks for `[all @ ..]` where `all` would suffice"
}

declare_lint_pass!(MiscEarlyLints => [
    UNNEEDED_FIELD_PATTERN,
    DUPLICATE_UNDERSCORE_ARGUMENT,
    MIXED_CASE_HEX_LITERALS,
    UNSEPARATED_LITERAL_SUFFIX,
    SEPARATED_LITERAL_SUFFIX,
    ZERO_PREFIXED_LITERAL,
    BUILTIN_TYPE_SHADOW,
    REDUNDANT_PATTERN,
    UNNEEDED_WILDCARD_PATTERN,
    REDUNDANT_AT_REST_PATTERN,
]);

impl EarlyLintPass for MiscEarlyLints {
    fn check_generics(&mut self, cx: &EarlyContext<'_>, generics: &Generics) {
        for param in &generics.params {
            builtin_type_shadow::check(cx, param);
        }
    }

    fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) {
        if pat.span.in_external_macro(cx.sess().source_map()) {
            return;
        }

        unneeded_field_pattern::check(cx, pat);
        redundant_pattern::check(cx, pat);
        redundant_at_rest_pattern::check(cx, pat);
        unneeded_wildcard_pattern::check(cx, pat);
    }

    fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) {
        let mut registered_names: FxHashMap<String, Span> = FxHashMap::default();

        for arg in &fn_kind.decl().inputs {
            if let PatKind::Ident(_, ident, None) = arg.pat.kind {
                let arg_name = ident.to_string();

                if let Some(arg_name) = arg_name.strip_prefix('_') {
                    if let Some(correspondence) = registered_names.get(arg_name) {
                        span_lint(
                            cx,
                            DUPLICATE_UNDERSCORE_ARGUMENT,
                            *correspondence,
                            format!(
                                "`{arg_name}` already exists, having another argument having almost the same \
                                 name makes code comprehension and documentation more difficult"
                            ),
                        );
                    }
                } else {
                    registered_names.insert(arg_name, arg.pat.span);
                }
            }
        }
    }

    fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
        if expr.span.in_external_macro(cx.sess().source_map()) {
            return;
        }

        if let ExprKind::Lit(lit) = expr.kind {
            MiscEarlyLints::check_lit(cx, lit, expr.span);
        }
    }
}

impl MiscEarlyLints {
    fn check_lit(cx: &EarlyContext<'_>, lit: token::Lit, span: Span) {
        // We test if first character in snippet is a number, because the snippet could be an expansion
        // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`.
        // Note that this check also covers special case that `line!()` is eagerly expanded by compiler.
        // See <https://github.com/rust-lang/rust-clippy/issues/4507> for a regression.
        // FIXME: Find a better way to detect those cases.
        let lit_snip = match snippet_opt(cx, span) {
            Some(snip) if snip.chars().next().is_some_and(|c| c.is_ascii_digit()) => snip,
            _ => return,
        };

        let lit_kind = LitKind::from_token_lit(lit);
        if let Ok(LitKind::Int(value, lit_int_type)) = lit_kind {
            let suffix = match lit_int_type {
                LitIntType::Signed(ty) => ty.name_str(),
                LitIntType::Unsigned(ty) => ty.name_str(),
                LitIntType::Unsuffixed => "",
            };
            literal_suffix::check(cx, span, &lit_snip, suffix, "integer");
            if lit_snip.starts_with("0x") {
                mixed_case_hex_literals::check(cx, span, suffix, &lit_snip);
            } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") {
                // nothing to do
            } else if value != 0 && lit_snip.starts_with('0') {
                zero_prefixed_literal::check(cx, span, &lit_snip);
            }
        } else if let Ok(LitKind::Float(_, LitFloatType::Suffixed(float_ty))) = lit_kind {
            let suffix = float_ty.name_str();
            literal_suffix::check(cx, span, &lit_snip, suffix, "float");
        }
    }
}
