| use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit}; | |
| use rustc_attr_ir::target::Target; | |
| use rustc_attr_ir::{ | |
| AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue, | |
| DocInline, HideOrShow, | |
| }; | |
| use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry}; | |
| use rustc_errors::{Applicability, msg}; | |
| use rustc_feature::AttributeStability; | |
| use rustc_session::diagnostics::feature_err; | |
| use rustc_span::{Span, Symbol, edition, sym}; | |
| use super::prelude::{ALL_TARGETS, AllowedTargets}; | |
| use super::{AcceptMapping, AttributeParser, template}; | |
| use crate::context::{AcceptContext, FinalizeContext}; | |
| use crate::diagnostics::{ | |
| AttrCrateLevelOnly, DocAliasBadChar, DocAliasDuplicated, DocAliasEmpty, DocAliasMalformed, | |
| DocAliasStartEnd, DocAttrNotCrateLevel, DocAttributeNotAttribute, DocAutoCfgExpectsHideOrShow, | |
| DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues, | |
| DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues, | |
| DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocKeywordNotKeyword, DocTestLiteral, | |
| DocTestTakesList, DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, | |
| DocUnknownPlugins, DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, | |
| IllFormedAttributeInput, MalformedDoc, UnusedDuplicate, | |
| }; | |
| use crate::parser::{ | |
| ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser, | |
| }; | |
| fn check_keyword(cx: &mut AcceptContext<'_, '_>, 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(cx: &mut AcceptContext<'_, '_>, attribute: Symbol, span: Span) -> bool { | |
| // FIXME: This should support attributes with namespace like `diagnostic::do_not_recommend`. | |
| if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains(&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( | |
| cx: &mut AcceptContext<'_, '_>, | |
| 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(cx: &mut AcceptContext<'_, '_>, span: Span) -> bool { | |
| if cx.shared.target != Target::Crate { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| AttrCrateLevelOnly, | |
| span, | |
| ); | |
| return false; | |
| } | |
| true | |
| } | |
| // FIXME: To be removed once merged and replace with `cx.expected_name_value(span, _name)`. | |
| fn expected_name_value(cx: &mut AcceptContext<'_, '_>, span: Span, _name: Option<Symbol>) { | |
| cx.emit_lint(rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, ExpectedNameValue, span); | |
| } | |
| // FIXME: remove this method once merged and use `cx.expected_no_args(span)` instead. | |
| fn expected_no_args(cx: &mut AcceptContext<'_, '_>, span: Span) { | |
| cx.emit_lint(rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, 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( | |
| cx: &mut AcceptContext<'_, '_>, | |
| span: Span, | |
| _actual_literal: Option<&MetaItemLit>, | |
| ) { | |
| cx.emit_lint(rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, MalformedDoc, span); | |
| } | |
| fn parse_keyword_and_attribute( | |
| cx: &mut AcceptContext<'_, '_>, | |
| path: &OwnedPathParser, | |
| args: &ArgParser, | |
| attr_value: &mut Option<(Symbol, Span)>, | |
| attr_name: Symbol, | |
| ) { | |
| let Some(nv) = args.as_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( | |
| &mut self, | |
| cx: &mut AcceptContext<'_, '_>, | |
| mip: &MetaItemParser, | |
| ) { | |
| let path = mip.path(); | |
| let args = mip.args(); | |
| match path.word_sym() { | |
| Some(sym::no_crate_inject) => { | |
| if let Err(span) = args.as_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, | |
| 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.as_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, | |
| MalformedDoc, | |
| span, | |
| ); | |
| return; | |
| }; | |
| // FIXME: convert list into a Vec of `AttributeKind` because current code is awful. | |
| for attr in list.mixed() { | |
| // Arguments of `attr` are checked via the span, so can be safely ignored | |
| attr.ignore_args(); | |
| self.attribute.test_attrs.push(attr.span()); | |
| } | |
| } | |
| Some(name) => { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocTestUnknown { name }, | |
| path.span(), | |
| ); | |
| } | |
| None => { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocTestLiteral, | |
| path.span(), | |
| ); | |
| } | |
| } | |
| } | |
| fn add_alias(&mut self, cx: &mut AcceptContext<'_, '_>, 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, | |
| DocAliasDuplicated { first_definition }, | |
| span, | |
| ); | |
| } | |
| self.attribute.aliases.insert(alias, span); | |
| } | |
| fn parse_alias( | |
| &mut self, | |
| cx: &mut AcceptContext<'_, '_>, | |
| 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) = cx.expect_string_literal(i) else { | |
| continue; | |
| }; | |
| self.add_alias(cx, alias, i.span()); | |
| } | |
| } | |
| ArgParser::NameValue(nv) => { | |
| let Some(alias) = cx.expect_string_literal(nv) else { | |
| return; | |
| }; | |
| self.add_alias(cx, alias, nv.value_span); | |
| } | |
| } | |
| } | |
| fn parse_inline( | |
| &mut self, | |
| cx: &mut AcceptContext<'_, '_>, | |
| path: &OwnedPathParser, | |
| args: &ArgParser, | |
| inline: DocInline, | |
| ) { | |
| if let Err(span) = args.as_no_args() { | |
| expected_no_args(cx, span); | |
| return; | |
| } | |
| self.attribute.inline.push((inline, path.span())); | |
| } | |
| fn parse_cfg(&mut self, cx: &mut AcceptContext<'_, '_>, 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); | |
| } | |
| } | |
| // Parses the `doc(auto_cfg(hide/show(..., values())))` attribute. | |
| fn parse_auto_cfg_values( | |
| &self, | |
| cx: &mut AcceptContext<'_, '_>, | |
| list: &MetaItemListParser, | |
| values: &mut Option<DocCfgHideShow>, | |
| ) { | |
| let mut cfg_values = DocCfgHideShow::new(); | |
| let mut values_set = FxHashSet::default(); | |
| for item in list.mixed() { | |
| match item { | |
| // If it's a string literal, all good. | |
| MetaItemOrLitParser::Lit(MetaItemLit { | |
| kind: LitKind::Str(symbol, _), | |
| span, | |
| .. | |
| }) => match &mut cfg_values { | |
| DocCfgHideShow::Any(any_span) => { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowValuesMix { value_span: *span }, | |
| *any_span, | |
| ); | |
| } | |
| DocCfgHideShow::List(symbols) => { | |
| if values_set.insert(symbol) { | |
| symbols.push(DocCfgHideShowValue::new(*symbol, *span)); | |
| } | |
| } | |
| }, | |
| // If it's any other kind of literal, then it's wrong and we emit a lint. | |
| MetaItemOrLitParser::Lit(lit) => cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol }, | |
| lit.span, | |
| ), | |
| // If it's a list, then only `any()` and `none()` are allowed and they must not | |
| // contain any item. | |
| MetaItemOrLitParser::MetaItemParser(sub_item) => { | |
| let Some(ident) = sub_item.ident() else { | |
| cx.adcx().expected_identifier(sub_item.path().span()); | |
| continue; | |
| }; | |
| if [sym::any, sym::none].contains(&ident.name) | |
| && let ArgParser::List(list) = sub_item.args() | |
| && list.mixed().count() == 0 | |
| { | |
| if ident.name == sym::any { | |
| if let DocCfgHideShow::List(values) = &cfg_values | |
| && let Some(value) = values.first() | |
| { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowValuesMix { value_span: value.span }, | |
| sub_item.span(), | |
| ); | |
| } else { | |
| cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span())); | |
| } | |
| } else { | |
| cfg_values.push_none(sub_item.span()); | |
| } | |
| } else { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowUnexpectedItem { attr_name: ident.name }, | |
| sub_item.span(), | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| *values = Some(cfg_values); | |
| } | |
| fn parse_auto_cfg( | |
| &mut self, | |
| cx: &mut AcceptContext<'_, '_>, | |
| path: &OwnedPathParser, | |
| args: &ArgParser, | |
| ) { | |
| match args { | |
| ArgParser::NoArgs => { | |
| self.attribute.auto_cfg_change.push((true, path.span())); | |
| } | |
| ArgParser::List(list) => { | |
| 'main: for meta in list.mixed() { | |
| let MetaItemOrLitParser::MetaItemParser(item) = meta else { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgExpectsHideOrShow, | |
| meta.span(), | |
| ); | |
| continue; | |
| }; | |
| // Only `hide` and `show` are allowed in `auto_cfg` if it's a list, and both | |
| // must be a list. | |
| 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, | |
| DocAutoCfgExpectsHideOrShow, | |
| item.span(), | |
| ); | |
| continue; | |
| } | |
| }; | |
| let ArgParser::List(list) = item.args() else { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowExpectsList { attr_name }, | |
| item.span(), | |
| ); | |
| continue; | |
| }; | |
| let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() }; | |
| let mut cfg_names = FxHashSet::default(); | |
| let mut values = None; | |
| for item in list.mixed() { | |
| let MetaItemOrLitParser::MetaItemParser(sub_item) = item else { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowUnexpectedItem { attr_name }, | |
| item.span(), | |
| ); | |
| continue 'main; | |
| }; | |
| match sub_item.args() { | |
| ArgParser::NoArgs if values.is_none() => { | |
| let Some(name) = sub_item.path().word_sym() else { | |
| cx.adcx().expected_identifier(sub_item.path().span()); | |
| continue 'main; | |
| }; | |
| cfg_names.insert(name); | |
| } | |
| // The only accepted list is `values()`. | |
| ArgParser::List(list) if values.is_none() => { | |
| let Some(sym::values) = sub_item.path().word_sym() else { | |
| cx.adcx().expected_identifier(sub_item.path().span()); | |
| continue 'main; | |
| }; | |
| if cfg_names.is_empty() { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowNoIdentBeforeValues, | |
| sub_item.span(), | |
| ); | |
| continue 'main; | |
| } | |
| self.parse_auto_cfg_values(cx, list, &mut values); | |
| } | |
| // No `name = value` is allowed. | |
| ArgParser::NameValue(_) => { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowUnexpectedItem { attr_name }, | |
| sub_item.span(), | |
| ); | |
| } | |
| // If `values()` was already used, no item should come after it. | |
| _ => { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocAutoCfgHideShowUnexpectedItemAfterValues, | |
| sub_item.span(), | |
| ); | |
| } | |
| } | |
| } | |
| let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span())); | |
| #[allow(rustc::potential_query_instability)] | |
| for cfg_name in &cfg_names { | |
| match cfg_hide_show.values.entry(*cfg_name) { | |
| IndexEntry::Vacant(v) => { | |
| v.insert(values.clone()); | |
| } | |
| IndexEntry::Occupied(mut o) => { | |
| o.get_mut().merge_with(&values); | |
| } | |
| } | |
| } | |
| 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, | |
| DocAutoCfgWrongLiteral, | |
| nv.value_span, | |
| ); | |
| return; | |
| }; | |
| self.attribute.auto_cfg_change.push((*bool_value, *span)); | |
| } | |
| } | |
| } | |
| fn parse_single_doc_attr_item(&mut self, cx: &mut AcceptContext<'_, '_>, mip: &MetaItemParser) { | |
| let path = mip.path(); | |
| let args = mip.args(); | |
| macro_rules! no_args { | |
| ($ident: ident) => {{ | |
| if let Err(span) = args.as_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.as_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.as_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.as_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.as_list() else { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| 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, | |
| MalformedDoc, | |
| lit.span, | |
| ); | |
| } | |
| } | |
| } | |
| } | |
| Some(sym::spotlight) => { | |
| let span = path.span(); | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocUnknownSpotlight { sugg_span: span }, | |
| span, | |
| ); | |
| } | |
| Some(sym::include) if let Some(nv) = args.as_name_value() => { | |
| let inner = match cx.attr_style { | |
| AttrStyle::Outer => "", | |
| AttrStyle::Inner => "!", | |
| }; | |
| let value = nv.value_as_lit().symbol; | |
| let span = path.span(); | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) }, | |
| span, | |
| ); | |
| } | |
| Some(name @ (sym::passes | sym::no_default_passes)) => { | |
| let span = path.span(); | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocUnknownPasses { name, note_span: span }, | |
| span, | |
| ); | |
| } | |
| Some(sym::plugins) => { | |
| let span = path.span(); | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocUnknownPlugins { label_span: span }, | |
| span, | |
| ); | |
| } | |
| Some(name) => { | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocUnknownAny { name }, | |
| path.span(), | |
| ); | |
| } | |
| None => { | |
| let full_name = | |
| path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>(); | |
| let name = Symbol::intern(&full_name); | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| DocUnknownAny { name }, | |
| path.span(), | |
| ); | |
| } | |
| } | |
| } | |
| fn accept_single_doc_attr(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) { | |
| match args { | |
| ArgParser::NoArgs => { | |
| let suggestions = cx.adcx().suggestions(); | |
| let span = cx.inner_span; | |
| cx.emit_lint( | |
| rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, | |
| IllFormedAttributeInput::new(&suggestions, None, None), | |
| span, | |
| ); | |
| } | |
| ArgParser::List(items) => { | |
| for i in items.mixed() { | |
| match i { | |
| MetaItemOrLitParser::MetaItemParser(mip) => { | |
| if self.nb_doc_attrs == 0 { | |
| self.attribute.first_span = cx.attr_span; | |
| } | |
| 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 AttributeParser for DocParser { | |
| const ATTRIBUTES: AcceptMapping<Self> = &[( | |
| &[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" | |
| ), | |
| AttributeStability::Stable, // Some parts of the attribute are unstable, manually checked in parser | |
| |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<'_, '_>) -> Option<AttributeKind> { | |
| if self.nb_doc_attrs != 0 { | |
| Some(AttributeKind::Doc(Box::new(self.attribute))) | |
| } else { | |
| None | |
| } | |
| } | |
| } |