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}