760 lines
28 KiB
Rust
760 lines
28 KiB
Rust
use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
|
|
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_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.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.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.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) => {{
|
|
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;
|
|
}
|
|
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),
|
|
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.suggestions();
|
|
let span = cx.attr_span;
|
|
cx.emit_lint(
|
|
rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
|
|
AttributeLintKind::IllFormedAttributeInput { suggestions, docs: 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
|
|
}
|
|
}
|
|
}
|