compiler/rustc_attr_parsing/src/attributes/doc.rs RUST 806 lines View on github.com → Search inside
1use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};2use rustc_attr_ir::target::Target;3use rustc_attr_ir::{4    AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue,5    DocInline, HideOrShow,6};7use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};8use rustc_errors::{Applicability, msg};9use rustc_feature::AttributeStability;10use rustc_lint_defs::builtin::{INVALID_DOC_ATTRIBUTES, UNUSED_ATTRIBUTES};11use rustc_session::diagnostics::feature_err;12use rustc_span::{Span, Symbol, edition, sym};1314use super::prelude::{ALL_TARGETS, AllowedTargets};15use super::{AcceptMapping, AttributeParser, template};16use crate::context::{AcceptContext, FinalizeContext};17use crate::diagnostics::{18    AttrCrateLevelOnly, DocAliasBadChar, DocAliasDuplicated, DocAliasEmpty, DocAliasMalformed,19    DocAliasStartEnd, DocAttrNotCrateLevel, DocAttributeNotAttribute, DocAutoCfgExpectsHideOrShow,20    DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues,21    DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues,22    DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocKeywordNotKeyword, DocTestLiteral,23    DocTestTakesList, DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses,24    DocUnknownPlugins, DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs,25    IllFormedAttributeInput, MalformedDoc, UnusedDuplicate,26};27use crate::parser::{28    ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser,29};3031fn check_keyword(cx: &mut AcceptContext<'_, '_>, keyword: Symbol, span: Span) -> bool {32    // FIXME: Once rustdoc can handle URL conflicts on case insensitive file systems, we33    // can remove the `SelfTy` case here, remove `sym::SelfTy`, and update the34    // `#[doc(keyword = "SelfTy")` attribute in `library/std/src/keyword_docs.rs`.35    if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)36        || keyword.is_weak()37        || keyword == sym::SelfTy38    {39        return true;40    }41    cx.emit_err(DocKeywordNotKeyword { span, keyword });42    false43}4445fn check_attribute(cx: &mut AcceptContext<'_, '_>, attribute: Symbol, span: Span) -> bool {46    // FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`.47    if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains(&attribute) {48        return true;49    }50    cx.emit_err(DocAttributeNotAttribute { span, attribute });51    false52}5354/// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.55fn check_attr_not_crate_level(56    cx: &mut AcceptContext<'_, '_>,57    span: Span,58    attr_name: Symbol,59) -> bool {60    if cx.shared.target == Target::Crate {61        cx.emit_err(DocAttrNotCrateLevel { span, attr_name });62        return false;63    }64    true65}6667/// Checks that an attribute is used at the crate level. Returns `true` if valid.68fn check_attr_crate_level(cx: &mut AcceptContext<'_, '_>, span: Span) -> bool {69    if cx.shared.target != Target::Crate {70        cx.emit_lint(INVALID_DOC_ATTRIBUTES, AttrCrateLevelOnly, span);71        return false;72    }73    true74}7576// FIXME: To be removed once merged and replace with `cx.expected_name_value(span, _name)`.77fn expected_name_value(cx: &mut AcceptContext<'_, '_>, span: Span, _name: Option<Symbol>) {78    cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNameValue, span);79}8081// FIXME: remove this method once merged and use `cx.expected_no_args(span)` instead.82fn expected_no_args(cx: &mut AcceptContext<'_, '_>, span: Span) {83    cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNoArgs, span);84}8586// FIXME: remove this method once merged and use `cx.expected_no_args(span)` instead.87// cx.expected_string_literal(span, _actual_literal);88fn expected_string_literal(89    cx: &mut AcceptContext<'_, '_>,90    span: Span,91    _actual_literal: Option<&MetaItemLit>,92) {93    cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span);94}9596fn parse_keyword_and_attribute(97    cx: &mut AcceptContext<'_, '_>,98    path: &OwnedPathParser,99    args: &ArgParser,100    attr_value: &mut Option<(Symbol, Span)>,101    attr_name: Symbol,102) {103    let Some(nv) = args.as_name_value() else {104        expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());105        return;106    };107108    let Some(value) = nv.value_as_str() else {109        expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));110        return;111    };112113    let ret = if attr_name == sym::keyword {114        check_keyword(cx, value, nv.value_span)115    } else {116        check_attribute(cx, value, nv.value_span)117    };118    if !ret {119        return;120    }121122    let span = path.span();123    if attr_value.is_some() {124        cx.adcx().duplicate_key(span, path.word_sym().unwrap());125        return;126    }127128    if !check_attr_not_crate_level(cx, span, attr_name) {129        return;130    }131132    *attr_value = Some((value, span));133}134135#[derive(Default, Debug)]136pub(crate) struct DocParser {137    attribute: DocAttribute,138    nb_doc_attrs: usize,139}140141impl DocParser {142    fn parse_single_test_doc_attr_item(143        &mut self,144        cx: &mut AcceptContext<'_, '_>,145        mip: &MetaItemParser,146    ) {147        let path = mip.path();148        let args = mip.args();149150        match path.word_sym() {151            Some(sym::no_crate_inject) => {152                if let Err(span) = args.as_no_args() {153                    expected_no_args(cx, span);154                    return;155                }156157                if let Some(used_span) = self.attribute.no_crate_inject {158                    let unused_span = path.span();159                    cx.emit_lint(160                        INVALID_DOC_ATTRIBUTES,161                        UnusedDuplicate { this: unused_span, other: used_span, warning: true },162                        unused_span,163                    );164                    return;165                }166167                if !check_attr_crate_level(cx, path.span()) {168                    return;169                }170171                self.attribute.no_crate_inject = Some(path.span())172            }173            Some(sym::attr) => {174                let Some(list) = args.as_list() else {175                    // FIXME: remove this method once merged and uncomment the line below instead.176                    // cx.expected_list(cx.attr_span, args);177                    let span = cx.attr_span;178                    cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span);179                    return;180                };181182                // FIXME: convert list into a Vec of `AttributeKind` because current code is awful.183                for attr in list.mixed() {184                    // Arguments of `attr` are checked via the span, so can be safely ignored185                    attr.ignore_args();186                    self.attribute.test_attrs.push(attr.span());187                }188            }189            Some(name) => {190                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocTestUnknown { name }, path.span());191            }192            None => {193                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocTestLiteral, path.span());194            }195        }196    }197198    fn add_alias(&mut self, cx: &mut AcceptContext<'_, '_>, alias: Symbol, span: Span) {199        let attr_str = "`#[doc(alias = \"...\")]`";200        if alias == sym::empty {201            cx.emit_err(DocAliasEmpty { span, attr_str });202            return;203        }204205        let alias_str = alias.as_str();206        if let Some(c) =207            alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))208        {209            cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });210            return;211        }212        if alias_str.starts_with(' ') || alias_str.ends_with(' ') {213            cx.emit_err(DocAliasStartEnd { span, attr_str });214            return;215        }216        if !check_attr_not_crate_level(cx, span, sym::alias) {217            return;218        }219220        if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {221            cx.emit_lint(UNUSED_ATTRIBUTES, DocAliasDuplicated { first_definition }, span);222        }223224        self.attribute.aliases.insert(alias, span);225    }226227    fn parse_alias(228        &mut self,229        cx: &mut AcceptContext<'_, '_>,230        path: &OwnedPathParser,231        args: &ArgParser,232    ) {233        match args {234            ArgParser::NoArgs => {235                cx.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) });236            }237            ArgParser::List(list) => {238                for i in list.mixed() {239                    let Some(alias) = cx.expect_string_literal(i) else {240                        continue;241                    };242243                    self.add_alias(cx, alias, i.span());244                }245            }246            ArgParser::NameValue(nv) => {247                let Some(alias) = cx.expect_string_literal(nv) else {248                    return;249                };250                self.add_alias(cx, alias, nv.value_span);251            }252        }253    }254255    fn parse_inline(256        &mut self,257        cx: &mut AcceptContext<'_, '_>,258        path: &OwnedPathParser,259        args: &ArgParser,260        inline: DocInline,261    ) {262        if let Err(span) = args.as_no_args() {263            expected_no_args(cx, span);264            return;265        }266267        self.attribute.inline.push((inline, path.span()));268    }269270    fn parse_cfg(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {271        // This function replaces cases like `cfg(all())` with `true`.272        fn simplify_cfg(cfg_entry: &mut CfgEntry) {273            match cfg_entry {274                CfgEntry::All(cfgs, span) if cfgs.is_empty() => {275                    *cfg_entry = CfgEntry::Bool(true, *span)276                }277                CfgEntry::Any(cfgs, span) if cfgs.is_empty() => {278                    *cfg_entry = CfgEntry::Bool(false, *span)279                }280                CfgEntry::Not(cfg, _) => simplify_cfg(cfg),281                _ => {}282            }283        }284        if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) {285            simplify_cfg(&mut cfg_entry);286            self.attribute.cfg.push(cfg_entry);287        }288    }289290    // Parses the `doc(auto_cfg(hide/show(..., values())))` attribute.291    fn parse_auto_cfg_values(292        &self,293        cx: &mut AcceptContext<'_, '_>,294        list: &MetaItemListParser,295        values: &mut Option<DocCfgHideShow>,296    ) {297        let mut cfg_values = DocCfgHideShow::new();298299        let mut values_set = FxHashSet::default();300        for item in list.mixed() {301            match item {302                // If it's a string literal, all good.303                MetaItemOrLitParser::Lit(MetaItemLit {304                    kind: LitKind::Str(symbol, _),305                    span,306                    ..307                }) => match &mut cfg_values {308                    DocCfgHideShow::Any(any_span) => {309                        cx.emit_lint(310                            INVALID_DOC_ATTRIBUTES,311                            DocAutoCfgHideShowValuesMix { value_span: *span },312                            *any_span,313                        );314                    }315                    DocCfgHideShow::List(symbols) => {316                        if values_set.insert(symbol) {317                            symbols.push(DocCfgHideShowValue::new(*symbol, *span));318                        }319                    }320                },321                // If it's any other kind of literal, then it's wrong and we emit a lint.322                MetaItemOrLitParser::Lit(lit) => cx.emit_lint(323                    INVALID_DOC_ATTRIBUTES,324                    DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol },325                    lit.span,326                ),327                // If it's a list, then only `any()` and `none()` are allowed and they must not328                // contain any item.329                MetaItemOrLitParser::MetaItemParser(sub_item) => {330                    let Some(ident) = sub_item.ident() else {331                        cx.adcx().expected_identifier(sub_item.path().span());332                        continue;333                    };334                    if [sym::any, sym::none].contains(&ident.name)335                        && let ArgParser::List(list) = sub_item.args()336                        && list.mixed().count() == 0337                    {338                        if ident.name == sym::any {339                            if let DocCfgHideShow::List(values) = &cfg_values340                                && let Some(value) = values.first()341                            {342                                cx.emit_lint(343                                    INVALID_DOC_ATTRIBUTES,344                                    DocAutoCfgHideShowValuesMix { value_span: value.span },345                                    sub_item.span(),346                                );347                            } else {348                                cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span()));349                            }350                        } else {351                            cfg_values.push_none(sub_item.span());352                        }353                    } else {354                        cx.emit_lint(355                            INVALID_DOC_ATTRIBUTES,356                            DocAutoCfgHideShowUnexpectedItem { attr_name: ident.name },357                            sub_item.span(),358                        );359                    }360                }361            }362        }363        *values = Some(cfg_values);364    }365366    fn parse_auto_cfg(367        &mut self,368        cx: &mut AcceptContext<'_, '_>,369        path: &OwnedPathParser,370        args: &ArgParser,371    ) {372        match args {373            ArgParser::NoArgs => {374                self.attribute.auto_cfg_change.push((true, path.span()));375            }376            ArgParser::List(list) => {377                'main: for meta in list.mixed() {378                    let MetaItemOrLitParser::MetaItemParser(item) = meta else {379                        cx.emit_lint(380                            INVALID_DOC_ATTRIBUTES,381                            DocAutoCfgExpectsHideOrShow,382                            meta.span(),383                        );384                        continue;385                    };386                    // Only `hide` and `show` are allowed in `auto_cfg` if it's a list, and both387                    // must be a list.388                    let (kind, attr_name) = match item.path().word_sym() {389                        Some(sym::hide) => (HideOrShow::Hide, sym::hide),390                        Some(sym::show) => (HideOrShow::Show, sym::show),391                        _ => {392                            cx.emit_lint(393                                INVALID_DOC_ATTRIBUTES,394                                DocAutoCfgExpectsHideOrShow,395                                item.span(),396                            );397                            continue;398                        }399                    };400                    let ArgParser::List(list) = item.args() else {401                        cx.emit_lint(402                            INVALID_DOC_ATTRIBUTES,403                            DocAutoCfgHideShowExpectsList { attr_name },404                            item.span(),405                        );406                        continue;407                    };408409                    let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() };410411                    let mut cfg_names = FxHashSet::default();412                    let mut values = None;413                    for item in list.mixed() {414                        let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {415                            cx.emit_lint(416                                INVALID_DOC_ATTRIBUTES,417                                DocAutoCfgHideShowUnexpectedItem { attr_name },418                                item.span(),419                            );420                            continue 'main;421                        };422                        match sub_item.args() {423                            ArgParser::NoArgs if values.is_none() => {424                                let Some(name) = sub_item.path().word_sym() else {425                                    cx.adcx().expected_identifier(sub_item.path().span());426                                    continue 'main;427                                };428                                cfg_names.insert(name);429                            }430                            // The only accepted list is `values()`.431                            ArgParser::List(list) if values.is_none() => {432                                let Some(sym::values) = sub_item.path().word_sym() else {433                                    cx.adcx().expected_identifier(sub_item.path().span());434                                    continue 'main;435                                };436                                if cfg_names.is_empty() {437                                    cx.emit_lint(438                                        INVALID_DOC_ATTRIBUTES,439                                        DocAutoCfgHideShowNoIdentBeforeValues,440                                        sub_item.span(),441                                    );442                                    continue 'main;443                                }444                                self.parse_auto_cfg_values(cx, list, &mut values);445                            }446                            // No `name = value` is allowed.447                            ArgParser::NameValue(_) => {448                                cx.emit_lint(449                                    INVALID_DOC_ATTRIBUTES,450                                    DocAutoCfgHideShowUnexpectedItem { attr_name },451                                    sub_item.span(),452                                );453                            }454                            // If `values()` was already used, no item should come after it.455                            _ => {456                                cx.emit_lint(457                                    INVALID_DOC_ATTRIBUTES,458                                    DocAutoCfgHideShowUnexpectedItemAfterValues,459                                    sub_item.span(),460                                );461                            }462                        }463                    }464465                    let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span()));466                    #[allow(rustc::potential_query_instability)]467                    for cfg_name in &cfg_names {468                        match cfg_hide_show.values.entry(*cfg_name) {469                            IndexEntry::Vacant(v) => {470                                v.insert(values.clone());471                            }472                            IndexEntry::Occupied(mut o) => {473                                o.get_mut().merge_with(&values);474                            }475                        }476                    }477                    self.attribute.auto_cfg.push((cfg_hide_show, path.span()));478                }479            }480            ArgParser::NameValue(nv) => {481                let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()482                else {483                    cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocAutoCfgWrongLiteral, nv.value_span);484                    return;485                };486                self.attribute.auto_cfg_change.push((*bool_value, *span));487            }488        }489    }490491    fn parse_single_doc_attr_item(&mut self, cx: &mut AcceptContext<'_, '_>, mip: &MetaItemParser) {492        let path = mip.path();493        let args = mip.args();494495        macro_rules! no_args {496            ($ident: ident) => {{497                if let Err(span) = args.as_no_args() {498                    expected_no_args(cx, span);499                    return;500                }501502                // FIXME: It's errorring when the attribute is passed multiple times on the command503                // line.504                // The right fix for this would be to only check this rule if the attribute is505                // not set on the command line but directly in the code.506                // if self.attribute.$ident.is_some() {507                //     cx.duplicate_key(path.span(), path.word_sym().unwrap());508                //     return;509                // }510511                self.attribute.$ident = Some(path.span());512            }};513        }514        macro_rules! no_args_and_not_crate_level {515            ($ident: ident) => {{516                if let Err(span) = args.as_no_args() {517                    expected_no_args(cx, span);518                    return;519                }520                let span = path.span();521                if !check_attr_not_crate_level(cx, span, sym::$ident) {522                    return;523                }524                self.attribute.$ident = Some(span);525            }};526        }527        macro_rules! no_args_and_crate_level {528            ($ident: ident) => {{529                no_args_and_crate_level!($ident, |span| {});530            }};531            ($ident: ident, |$span:ident| $extra_validation:block) => {{532                if let Err(span) = args.as_no_args() {533                    expected_no_args(cx, span);534                    return;535                }536                let $span = path.span();537                if !check_attr_crate_level(cx, $span) {538                    return;539                }540                $extra_validation541                self.attribute.$ident = Some($span);542            }};543        }544        macro_rules! string_arg_and_crate_level {545            ($ident: ident) => {{546                let Some(nv) = args.as_name_value() else {547                    expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());548                    return;549                };550551                let Some(s) = nv.value_as_str() else {552                    expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));553                    return;554                };555556                if !check_attr_crate_level(cx, path.span()) {557                    return;558                }559560                // FIXME: It's errorring when the attribute is passed multiple times on the command561                // line.562                // The right fix for this would be to only check this rule if the attribute is563                // not set on the command line but directly in the code.564                // if self.attribute.$ident.is_some() {565                //     cx.duplicate_key(path.span(), path.word_sym().unwrap());566                //     return;567                // }568569                self.attribute.$ident = Some((s, path.span()));570            }};571        }572573        match path.word_sym() {574            Some(sym::alias) => self.parse_alias(cx, path, args),575            Some(sym::hidden) => no_args!(hidden),576            Some(sym::html_favicon_url) => string_arg_and_crate_level!(html_favicon_url),577            Some(sym::html_logo_url) => string_arg_and_crate_level!(html_logo_url),578            Some(sym::html_no_source) => no_args_and_crate_level!(html_no_source),579            Some(sym::html_playground_url) => string_arg_and_crate_level!(html_playground_url),580            Some(sym::html_root_url) => string_arg_and_crate_level!(html_root_url),581            Some(sym::issue_tracker_base_url) => {582                string_arg_and_crate_level!(issue_tracker_base_url)583            }584            Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),585            Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),586            Some(sym::masked) => no_args!(masked),587            Some(sym::cfg) => self.parse_cfg(cx, args),588            Some(sym::notable_trait) => no_args!(notable_trait),589            Some(sym::keyword) => parse_keyword_and_attribute(590                cx,591                path,592                args,593                &mut self.attribute.keyword,594                sym::keyword,595            ),596            Some(sym::attribute) => parse_keyword_and_attribute(597                cx,598                path,599                args,600                &mut self.attribute.attribute,601                sym::attribute,602            ),603            Some(sym::fake_variadic) => no_args_and_not_crate_level!(fake_variadic),604            Some(sym::search_unbox) => no_args_and_not_crate_level!(search_unbox),605            Some(sym::rust_logo) => no_args_and_crate_level!(rust_logo, |span| {606                if !cx.features().rustdoc_internals() {607                    feature_err(608                        cx.sess(),609                        sym::rustdoc_internals,610                        span,611                        msg!("the `#[doc(rust_logo)]` attribute is used for Rust branding"),612                    )613                    .emit();614                }615            }),616            Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args),617            Some(sym::test) => {618                let Some(list) = args.as_list() else {619                    cx.emit_lint(620                        INVALID_DOC_ATTRIBUTES,621                        DocTestTakesList,622                        args.span().unwrap_or(path.span()),623                    );624                    return;625                };626627                for i in list.mixed() {628                    match i {629                        MetaItemOrLitParser::MetaItemParser(mip) => {630                            self.parse_single_test_doc_attr_item(cx, mip);631                        }632                        MetaItemOrLitParser::Lit(lit) => {633                            // FIXME: remove this method once merged and uncomment the line634                            // below instead.635                            // cx.unexpected_literal(lit.span);636                            cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, lit.span);637                        }638                    }639                }640            }641            Some(sym::spotlight) => {642                let span = path.span();643                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownSpotlight { sugg_span: span }, span);644            }645            Some(sym::include) if let Some(nv) = args.as_name_value() => {646                let inner = match cx.attr_style {647                    AttrStyle::Outer => "",648                    AttrStyle::Inner => "!",649                };650                let value = nv.value_as_lit().symbol;651                let span = path.span();652                cx.emit_lint(653                    INVALID_DOC_ATTRIBUTES,654                    DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) },655                    span,656                );657            }658            Some(name @ (sym::passes | sym::no_default_passes)) => {659                let span = path.span();660                cx.emit_lint(661                    INVALID_DOC_ATTRIBUTES,662                    DocUnknownPasses { name, note_span: span },663                    span,664                );665            }666            Some(sym::plugins) => {667                let span = path.span();668                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownPlugins { label_span: span }, span);669            }670            Some(name) => {671                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownAny { name }, path.span());672            }673            None => {674                let full_name =675                    path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();676                let name = Symbol::intern(&full_name);677                cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownAny { name }, path.span());678            }679        }680    }681682    fn accept_single_doc_attr(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {683        match args {684            ArgParser::NoArgs => {685                let suggestions = cx.adcx().suggestions();686                let span = cx.inner_span;687                cx.emit_lint(688                    INVALID_DOC_ATTRIBUTES,689                    IllFormedAttributeInput::new(&suggestions, None, None),690                    span,691                );692            }693            ArgParser::List(items) => {694                for i in items.mixed() {695                    match i {696                        MetaItemOrLitParser::MetaItemParser(mip) => {697                            if self.nb_doc_attrs == 0 {698                                self.attribute.first_span = cx.attr_span;699                            }700                            self.nb_doc_attrs += 1;701                            self.parse_single_doc_attr_item(cx, mip);702                        }703                        MetaItemOrLitParser::Lit(lit) => {704                            expected_name_value(cx, lit.span, None);705                        }706                    }707                }708            }709            ArgParser::NameValue(nv) => {710                if nv.value_as_str().is_none() {711                    expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));712                } else {713                    unreachable!(714                        "Should have been handled at the same time as sugar-syntaxed doc comments"715                    );716                }717            }718        }719    }720}721722impl AttributeParser for DocParser {723    const ATTRIBUTES: AcceptMapping<Self> = &[(724        &[sym::doc],725        template!(726            List: &[727                "alias",728                "attribute",729                "hidden",730                "html_favicon_url",731                "html_logo_url",732                "html_no_source",733                "html_playground_url",734                "html_root_url",735                "issue_tracker_base_url",736                "inline",737                "no_inline",738                "masked",739                "cfg",740                "notable_trait",741                "keyword",742                "fake_variadic",743                "search_unbox",744                "rust_logo",745                "auto_cfg",746                "test",747                "spotlight",748                "include",749                "no_default_passes",750                "passes",751                "plugins",752            ],753            NameValueStr: "string"754        ),755        AttributeStability::Stable, // Some parts of the attribute are unstable, manually checked in parser756        |this, cx, args| {757            this.accept_single_doc_attr(cx, args);758        },759    )];760    // FIXME: Currently emitted from 2 different places, generating duplicated warnings.761    const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(ALL_TARGETS);762    // const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowListWarnRest(&[763    //     Allow(Target::ExternCrate),764    //     Allow(Target::Use),765    //     Allow(Target::Static),766    //     Allow(Target::Const),767    //     Allow(Target::Fn),768    //     Allow(Target::Mod),769    //     Allow(Target::ForeignMod),770    //     Allow(Target::TyAlias),771    //     Allow(Target::Enum),772    //     Allow(Target::Variant),773    //     Allow(Target::Struct),774    //     Allow(Target::Field),775    //     Allow(Target::Union),776    //     Allow(Target::Trait),777    //     Allow(Target::TraitAlias),778    //     Allow(Target::Impl { of_trait: true }),779    //     Allow(Target::Impl { of_trait: false }),780    //     Allow(Target::AssocConst(AssocCtxt::Impl { of_trait: false })),781    //     Allow(Target::AssocConst(AssocCtxt::Trait)),782    //     Allow(Target::AssocConst(AssocCtxt::Impl { of_trait: true })),783    //     Allow(Target::Method(MethodKind::Inherent)),784    //     Allow(Target::Method(MethodKind::Trait { body: true })),785    //     Allow(Target::Method(MethodKind::Trait { body: false })),786    //     Allow(Target::Method(MethodKind::TraitImpl)),787    //     Allow(Target::AssocTy(AssocCtxt::Impl { of_trait: false })),788    //     Allow(Target::AssocTy(AssocCtxt::Trait)),789    //     Allow(Target::AssocTy(AssocCtxt::Impl { of_trait: true })),790    //     Allow(Target::ForeignFn),791    //     Allow(Target::ForeignStatic),792    //     Allow(Target::ForeignTy),793    //     Allow(Target::MacroDef),794    //     Allow(Target::Crate),795    //     Error(Target::WherePredicate),796    // ]);797798    fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {799        if self.nb_doc_attrs != 0 {800            Some(AttributeKind::Doc(Box::new(self.attribute)))801        } else {802            None803        }804    }805}

Code quality findings 10

Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
cx.adcx().duplicate_key(span, path.word_sym().unwrap());
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
if [sym::any, sym::none].contains(&ident.name)
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
// cx.duplicate_key(path.span(), path.word_sym().unwrap());
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
// cx.duplicate_key(path.span(), path.word_sym().unwrap());
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
self.attribute.test_attrs.push(attr.span());
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
match cfg_entry {
Info: Ensure 'match' statements are exhaustive. If matching on enums, consider adding a wildcard arm `_ => {}` only if necessary and intentional, as it suppresses warnings about unhandled variants.
info correctness match-wildcard
let (kind, attr_name) = match item.path().word_sym() {
Info: Usage of `#[allow(...)]` suppresses compiler lints. Ensure the allowance is justified, well-scoped, and ideally temporary. Overuse can hide potential issues.
info maintainability allow-lint
#[allow(rustc::potential_query_instability)]
Performance Info: Frequent cloning, especially of Strings, Vecs, or other heap-allocated types inside loops, can be expensive. Consider using references/borrowing where possible.
info performance clone-in-loop
v.insert(values.clone());
Performance Info: Calling .push() repeatedly inside a loop without prior capacity reservation can lead to multiple reallocations. Consider using `Vec::with_capacity(n)` or `vec.reserve(n)` if the approximate number of elements is known.
info performance push-without-reserve
self.attribute.auto_cfg.push((cfg_hide_show, path.span()));

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.