use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
use rustc_errors::msg;
use rustc_feature::template;
use rustc_hir::Target;
use rustc_hir::attrs::{
    AttributeKind, CfgEntry, CfgHideShow, CfgInfo, DocAttribute, DocInline, HideOrShow,
};
use rustc_hir::lints::AttributeLintKind;
use rustc_session::parse::feature_err;
use rustc_span::{Span, Symbol, edition, sym};
use thin_vec::ThinVec;

use super::prelude::{ALL_TARGETS, AllowedTargets};
use super::{AcceptMapping, AttributeParser};
use crate::context::{AcceptContext, FinalizeContext, Stage};
use crate::parser::{ArgParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser};
use crate::session_diagnostics::{
    DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttrNotCrateLevel,
    DocAttributeNotAttribute, DocKeywordNotKeyword,
};

fn check_keyword<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, keyword: Symbol, span: Span) -> bool {
    // FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we
    // can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the
    // `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.
    if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)
        || keyword.is_weak()
        || keyword == sym::SelfTy
    {
        return true;
    }
    cx.emit_err(DocKeywordNotKeyword { span, keyword });
    false
}

fn check_attribute<S: Stage>(
    cx: &mut AcceptContext<'_, '_, S>,
    attribute: Symbol,
    span: Span,
) -> bool {
    // FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.
    if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains_key(&attribute) {
        return true;
    }
    cx.emit_err(DocAttributeNotAttribute { span, attribute });
    false
}

/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
fn check_attr_not_crate_level<S: Stage>(
    cx: &mut AcceptContext<'_, '_, S>,
    span: Span,
    attr_name: Symbol,
) -> bool {
    if cx.shared.target == Target::Crate {
        cx.emit_err(DocAttrNotCrateLevel { span, attr_name });
        return false;
    }
    true
}

/// Checks that an attribute is used at the crate level. Returns `true` if valid.
fn check_attr_crate_level<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, span: Span) -> bool {
    if cx.shared.target != Target::Crate {
        cx.emit_lint(
            rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
            AttributeLintKind::AttrCrateLevelOnly,
            span,
        );
        return false;
    }
    true
}

// FIXME: To be removed once merged and replace with `cx.expected_name_value(span, _name)`.
fn expected_name_value<S: Stage>(
    cx: &mut AcceptContext<'_, '_, S>,
    span: Span,
    _name: Option<Symbol>,
) {
    cx.emit_lint(
        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
        AttributeLintKind::ExpectedNameValue,
        span,
    );
}

// FIXME: remove this method once merged and use `cx.expected_no_args(span)` instead.
fn expected_no_args<S: Stage>(cx: &mut AcceptContext<'_, '_, S>, span: Span) {
    cx.emit_lint(
        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
        AttributeLintKind::ExpectedNoArgs,
        span,
    );
}

// FIXME: remove this method once merged and use `cx.expected_no_args(span)` instead.
// cx.expected_string_literal(span, _actual_literal);
fn expected_string_literal<S: Stage>(
    cx: &mut AcceptContext<'_, '_, S>,
    span: Span,
    _actual_literal: Option<&MetaItemLit>,
) {
    cx.emit_lint(
        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
        AttributeLintKind::MalformedDoc,
        span,
    );
}

fn parse_keyword_and_attribute<S: Stage>(
    cx: &mut AcceptContext<'_, '_, S>,
    path: &OwnedPathParser,
    args: &ArgParser,
    attr_value: &mut Option<(Symbol, Span)>,
    attr_name: Symbol,
) {
    let Some(nv) = args.name_value() else {
        expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
        return;
    };

    let Some(value) = nv.value_as_str() else {
        expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
        return;
    };

    let ret = if attr_name == sym::keyword {
        check_keyword(cx, value, nv.value_span)
    } else {
        check_attribute(cx, value, nv.value_span)
    };
    if !ret {
        return;
    }

    let span = path.span();
    if attr_value.is_some() {
        cx.adcx().duplicate_key(span, path.word_sym().unwrap());
        return;
    }

    if !check_attr_not_crate_level(cx, span, attr_name) {
        return;
    }

    *attr_value = Some((value, span));
}

#[derive(Default, Debug)]
pub(crate) struct DocParser {
    attribute: DocAttribute,
    nb_doc_attrs: usize,
}

impl DocParser {
    fn parse_single_test_doc_attr_item<S: Stage>(
        &mut self,
        cx: &mut AcceptContext<'_, '_, S>,
        mip: &MetaItemParser,
    ) {
        let path = mip.path();
        let args = mip.args();

        match path.word_sym() {
            Some(sym::no_crate_inject) => {
                if let Err(span) = args.no_args() {
                    expected_no_args(cx, span);
                    return;
                }

                if let Some(used_span) = self.attribute.no_crate_inject {
                    let unused_span = path.span();
                    cx.emit_lint(
                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                        AttributeLintKind::UnusedDuplicate {
                            this: unused_span,
                            other: used_span,
                            warning: true,
                        },
                        unused_span,
                    );
                    return;
                }

                if !check_attr_crate_level(cx, path.span()) {
                    return;
                }

                self.attribute.no_crate_inject = Some(path.span())
            }
            Some(sym::attr) => {
                let Some(list) = args.list() else {
                    // FIXME: remove this method once merged and uncomment the line below instead.
                    // cx.expected_list(cx.attr_span, args);
                    let span = cx.attr_span;
                    cx.emit_lint(
                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                        AttributeLintKind::MalformedDoc,
                        span,
                    );
                    return;
                };

                // FIXME: convert list into a Vec of `AttributeKind` because current code is awful.
                for attr in list.mixed() {
                    self.attribute.test_attrs.push(attr.span());
                }
            }
            Some(name) => {
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::DocTestUnknown { name },
                    path.span(),
                );
            }
            None => {
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::DocTestLiteral,
                    path.span(),
                );
            }
        }
    }

    fn add_alias<S: Stage>(
        &mut self,
        cx: &mut AcceptContext<'_, '_, S>,
        alias: Symbol,
        span: Span,
    ) {
        let attr_str = "`#[doc(alias = \"...\")]`";
        if alias == sym::empty {
            cx.emit_err(DocAliasEmpty { span, attr_str });
            return;
        }

        let alias_str = alias.as_str();
        if let Some(c) =
            alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
        {
            cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });
            return;
        }
        if alias_str.starts_with(' ') || alias_str.ends_with(' ') {
            cx.emit_err(DocAliasStartEnd { span, attr_str });
            return;
        }
        if !check_attr_not_crate_level(cx, span, sym::alias) {
            return;
        }

        if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
            cx.emit_lint(
                rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
                AttributeLintKind::DuplicateDocAlias { first_definition },
                span,
            );
        }

        self.attribute.aliases.insert(alias, span);
    }

    fn parse_alias<S: Stage>(
        &mut self,
        cx: &mut AcceptContext<'_, '_, S>,
        path: &OwnedPathParser,
        args: &ArgParser,
    ) {
        match args {
            ArgParser::NoArgs => {
                cx.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) });
            }
            ArgParser::List(list) => {
                for i in list.mixed() {
                    let Some(alias) = i.lit().and_then(|i| i.value_str()) else {
                        cx.adcx().expected_string_literal(i.span(), i.lit());
                        continue;
                    };

                    self.add_alias(cx, alias, i.span());
                }
            }
            ArgParser::NameValue(nv) => {
                let Some(alias) = nv.value_as_str() else {
                    cx.adcx().expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
                    return;
                };
                self.add_alias(cx, alias, nv.value_span);
            }
        }
    }

    fn parse_inline<S: Stage>(
        &mut self,
        cx: &mut AcceptContext<'_, '_, S>,
        path: &OwnedPathParser,
        args: &ArgParser,
        inline: DocInline,
    ) {
        if let Err(span) = args.no_args() {
            expected_no_args(cx, span);
            return;
        }

        self.attribute.inline.push((inline, path.span()));
    }

    fn parse_cfg<S: Stage>(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) {
        // This function replaces cases like `cfg(all())` with `true`.
        fn simplify_cfg(cfg_entry: &mut CfgEntry) {
            match cfg_entry {
                CfgEntry::All(cfgs, span) if cfgs.is_empty() => {
                    *cfg_entry = CfgEntry::Bool(true, *span)
                }
                CfgEntry::Any(cfgs, span) if cfgs.is_empty() => {
                    *cfg_entry = CfgEntry::Bool(false, *span)
                }
                CfgEntry::Not(cfg, _) => simplify_cfg(cfg),
                _ => {}
            }
        }
        if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) {
            simplify_cfg(&mut cfg_entry);
            self.attribute.cfg.push(cfg_entry);
        }
    }

    fn parse_auto_cfg<S: Stage>(
        &mut self,
        cx: &mut AcceptContext<'_, '_, S>,
        path: &OwnedPathParser,
        args: &ArgParser,
    ) {
        match args {
            ArgParser::NoArgs => {
                self.attribute.auto_cfg_change.push((true, path.span()));
            }
            ArgParser::List(list) => {
                for meta in list.mixed() {
                    let MetaItemOrLitParser::MetaItemParser(item) = meta else {
                        cx.emit_lint(
                            rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                            AttributeLintKind::DocAutoCfgExpectsHideOrShow,
                            meta.span(),
                        );
                        continue;
                    };
                    let (kind, attr_name) = match item.path().word_sym() {
                        Some(sym::hide) => (HideOrShow::Hide, sym::hide),
                        Some(sym::show) => (HideOrShow::Show, sym::show),
                        _ => {
                            cx.emit_lint(
                                rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                                AttributeLintKind::DocAutoCfgExpectsHideOrShow,
                                item.span(),
                            );
                            continue;
                        }
                    };
                    let ArgParser::List(list) = item.args() else {
                        cx.emit_lint(
                            rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                            AttributeLintKind::DocAutoCfgHideShowExpectsList { attr_name },
                            item.span(),
                        );
                        continue;
                    };

                    let mut cfg_hide_show = CfgHideShow { kind, values: ThinVec::new() };

                    for item in list.mixed() {
                        let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
                            cx.emit_lint(
                                rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                                AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { attr_name },
                                item.span(),
                            );
                            continue;
                        };
                        match sub_item.args() {
                            a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
                                let Some(name) = sub_item.path().word_sym() else {
                                    // FIXME: remove this method once merged and uncomment the line
                                    // below instead.
                                    // cx.expected_identifier(sub_item.path().span());
                                    cx.emit_lint(
                                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                                        AttributeLintKind::MalformedDoc,
                                        sub_item.path().span(),
                                    );
                                    continue;
                                };
                                if let Ok(CfgEntry::NameValue { name, value, .. }) =
                                    super::cfg::parse_name_value(
                                        name,
                                        sub_item.path().span(),
                                        a.name_value(),
                                        sub_item.span(),
                                        cx,
                                    )
                                {
                                    cfg_hide_show.values.push(CfgInfo {
                                        name,
                                        name_span: sub_item.path().span(),
                                        // If `value` is `Some`, `a.name_value()` will always return
                                        // `Some` as well.
                                        value: value
                                            .map(|v| (v, a.name_value().unwrap().value_span)),
                                    })
                                }
                            }
                            _ => {
                                cx.emit_lint(
                                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                                    AttributeLintKind::DocAutoCfgHideShowUnexpectedItem {
                                        attr_name,
                                    },
                                    sub_item.span(),
                                );
                                continue;
                            }
                        }
                    }
                    self.attribute.auto_cfg.push((cfg_hide_show, path.span()));
                }
            }
            ArgParser::NameValue(nv) => {
                let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()
                else {
                    cx.emit_lint(
                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                        AttributeLintKind::DocAutoCfgWrongLiteral,
                        nv.value_span,
                    );
                    return;
                };
                self.attribute.auto_cfg_change.push((*bool_value, *span));
            }
        }
    }

    fn parse_single_doc_attr_item<S: Stage>(
        &mut self,
        cx: &mut AcceptContext<'_, '_, S>,
        mip: &MetaItemParser,
    ) {
        let path = mip.path();
        let args = mip.args();

        macro_rules! no_args {
            ($ident: ident) => {{
                if let Err(span) = args.no_args() {
                    expected_no_args(cx, span);
                    return;
                }

                // FIXME: It's errorring when the attribute is passed multiple times on the command
                // line.
                // The right fix for this would be to only check this rule if the attribute is
                // not set on the command line but directly in the code.
                // if self.attribute.$ident.is_some() {
                //     cx.duplicate_key(path.span(), path.word_sym().unwrap());
                //     return;
                // }

                self.attribute.$ident = Some(path.span());
            }};
        }
        macro_rules! no_args_and_not_crate_level {
            ($ident: ident) => {{
                if let Err(span) = args.no_args() {
                    expected_no_args(cx, span);
                    return;
                }
                let span = path.span();
                if !check_attr_not_crate_level(cx, span, sym::$ident) {
                    return;
                }
                self.attribute.$ident = Some(span);
            }};
        }
        macro_rules! no_args_and_crate_level {
            ($ident: ident) => {{
                no_args_and_crate_level!($ident, |span| {});
            }};
            ($ident: ident, |$span:ident| $extra_validation:block) => {{
                if let Err(span) = args.no_args() {
                    expected_no_args(cx, span);
                    return;
                }
                let $span = path.span();
                if !check_attr_crate_level(cx, $span) {
                    return;
                }
                $extra_validation
                self.attribute.$ident = Some($span);
            }};
        }
        macro_rules! string_arg_and_crate_level {
            ($ident: ident) => {{
                let Some(nv) = args.name_value() else {
                    expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
                    return;
                };

                let Some(s) = nv.value_as_str() else {
                    expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
                    return;
                };

                if !check_attr_crate_level(cx, path.span()) {
                    return;
                }

                // FIXME: It's errorring when the attribute is passed multiple times on the command
                // line.
                // The right fix for this would be to only check this rule if the attribute is
                // not set on the command line but directly in the code.
                // if self.attribute.$ident.is_some() {
                //     cx.duplicate_key(path.span(), path.word_sym().unwrap());
                //     return;
                // }

                self.attribute.$ident = Some((s, path.span()));
            }};
        }

        match path.word_sym() {
            Some(sym::alias) => self.parse_alias(cx, path, args),
            Some(sym::hidden) => no_args!(hidden),
            Some(sym::html_favicon_url) => string_arg_and_crate_level!(html_favicon_url),
            Some(sym::html_logo_url) => string_arg_and_crate_level!(html_logo_url),
            Some(sym::html_no_source) => no_args_and_crate_level!(html_no_source),
            Some(sym::html_playground_url) => string_arg_and_crate_level!(html_playground_url),
            Some(sym::html_root_url) => string_arg_and_crate_level!(html_root_url),
            Some(sym::issue_tracker_base_url) => {
                string_arg_and_crate_level!(issue_tracker_base_url)
            }
            Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
            Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
            Some(sym::masked) => no_args!(masked),
            Some(sym::cfg) => self.parse_cfg(cx, args),
            Some(sym::notable_trait) => no_args!(notable_trait),
            Some(sym::keyword) => parse_keyword_and_attribute(
                cx,
                path,
                args,
                &mut self.attribute.keyword,
                sym::keyword,
            ),
            Some(sym::attribute) => parse_keyword_and_attribute(
                cx,
                path,
                args,
                &mut self.attribute.attribute,
                sym::attribute,
            ),
            Some(sym::fake_variadic) => no_args_and_not_crate_level!(fake_variadic),
            Some(sym::search_unbox) => no_args_and_not_crate_level!(search_unbox),
            Some(sym::rust_logo) => no_args_and_crate_level!(rust_logo, |span| {
                if !cx.features().rustdoc_internals() {
                    feature_err(
                        cx.sess(),
                        sym::rustdoc_internals,
                        span,
                        msg!("the `#[doc(rust_logo)]` attribute is used for Rust branding"),
                    )
                    .emit();
                }
            }),
            Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args),
            Some(sym::test) => {
                let Some(list) = args.list() else {
                    cx.emit_lint(
                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                        AttributeLintKind::DocTestTakesList,
                        args.span().unwrap_or(path.span()),
                    );
                    return;
                };

                for i in list.mixed() {
                    match i {
                        MetaItemOrLitParser::MetaItemParser(mip) => {
                            self.parse_single_test_doc_attr_item(cx, mip);
                        }
                        MetaItemOrLitParser::Lit(lit) => {
                            // FIXME: remove this method once merged and uncomment the line
                            // below instead.
                            // cx.unexpected_literal(lit.span);
                            cx.emit_lint(
                                rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                                AttributeLintKind::MalformedDoc,
                                lit.span,
                            );
                        }
                    }
                }
            }
            Some(sym::spotlight) => {
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::DocUnknownSpotlight { span: path.span() },
                    path.span(),
                );
            }
            Some(sym::include) if let Some(nv) = args.name_value() => {
                let inner = match cx.attr_style {
                    AttrStyle::Outer => "",
                    AttrStyle::Inner => "!",
                };
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::DocUnknownInclude {
                        inner,
                        value: nv.value_as_lit().symbol,
                        span: path.span(),
                    },
                    path.span(),
                );
            }
            Some(name @ (sym::passes | sym::no_default_passes)) => {
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::DocUnknownPasses { name, span: path.span() },
                    path.span(),
                );
            }
            Some(sym::plugins) => {
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::DocUnknownPlugins { span: path.span() },
                    path.span(),
                );
            }
            Some(name) => {
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::DocUnknownAny { name },
                    path.span(),
                );
            }
            None => {
                let full_name =
                    path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::DocUnknownAny { name: Symbol::intern(&full_name) },
                    path.span(),
                );
            }
        }
    }

    fn accept_single_doc_attr<S: Stage>(
        &mut self,
        cx: &mut AcceptContext<'_, '_, S>,
        args: &ArgParser,
    ) {
        match args {
            ArgParser::NoArgs => {
                let suggestions = cx.adcx().suggestions();
                let span = cx.attr_span;
                cx.emit_lint(
                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
                    AttributeLintKind::IllFormedAttributeInput {
                        suggestions,
                        docs: None,
                        help: None,
                    },
                    span,
                );
            }
            ArgParser::List(items) => {
                for i in items.mixed() {
                    match i {
                        MetaItemOrLitParser::MetaItemParser(mip) => {
                            self.nb_doc_attrs += 1;
                            self.parse_single_doc_attr_item(cx, mip);
                        }
                        MetaItemOrLitParser::Lit(lit) => {
                            expected_name_value(cx, lit.span, None);
                        }
                    }
                }
            }
            ArgParser::NameValue(nv) => {
                if nv.value_as_str().is_none() {
                    expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
                } else {
                    unreachable!(
                        "Should have been handled at the same time as sugar-syntaxed doc comments"
                    );
                }
            }
        }
    }
}

impl<S: Stage> AttributeParser<S> for DocParser {
    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
        &[sym::doc],
        template!(
            List: &[
                "alias",
                "attribute",
                "hidden",
                "html_favicon_url",
                "html_logo_url",
                "html_no_source",
                "html_playground_url",
                "html_root_url",
                "issue_tracker_base_url",
                "inline",
                "no_inline",
                "masked",
                "cfg",
                "notable_trait",
                "keyword",
                "fake_variadic",
                "search_unbox",
                "rust_logo",
                "auto_cfg",
                "test",
                "spotlight",
                "include",
                "no_default_passes",
                "passes",
                "plugins",
            ],
            NameValueStr: "string"
        ),
        |this, cx, args| {
            this.accept_single_doc_attr(cx, args);
        },
    )];
    // FIXME: Currently emitted from 2 different places, generating duplicated warnings.
    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
    // const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
    //     Allow(Target::ExternCrate),
    //     Allow(Target::Use),
    //     Allow(Target::Static),
    //     Allow(Target::Const),
    //     Allow(Target::Fn),
    //     Allow(Target::Mod),
    //     Allow(Target::ForeignMod),
    //     Allow(Target::TyAlias),
    //     Allow(Target::Enum),
    //     Allow(Target::Variant),
    //     Allow(Target::Struct),
    //     Allow(Target::Field),
    //     Allow(Target::Union),
    //     Allow(Target::Trait),
    //     Allow(Target::TraitAlias),
    //     Allow(Target::Impl { of_trait: true }),
    //     Allow(Target::Impl { of_trait: false }),
    //     Allow(Target::AssocConst),
    //     Allow(Target::Method(MethodKind::Inherent)),
    //     Allow(Target::Method(MethodKind::Trait { body: true })),
    //     Allow(Target::Method(MethodKind::Trait { body: false })),
    //     Allow(Target::Method(MethodKind::TraitImpl)),
    //     Allow(Target::AssocTy),
    //     Allow(Target::ForeignFn),
    //     Allow(Target::ForeignStatic),
    //     Allow(Target::ForeignTy),
    //     Allow(Target::MacroDef),
    //     Allow(Target::Crate),
    //     Error(Target::WherePredicate),
    // ]);

    fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
        if self.nb_doc_attrs != 0 {
            Some(AttributeKind::Doc(Box::new(self.attribute)))
        } else {
            None
        }
    }
}
