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};
13
14use 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};
30
31fn check_keyword(cx: &mut AcceptContext<'_, '_>, keyword: Symbol, span: Span) -> bool {
32 if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)
36 || keyword.is_weak()
37 || keyword == sym::SelfTy
38 {
39 return true;
40 }
41 cx.emit_err(DocKeywordNotKeyword { span, keyword });
42 false
43}
44
45fn check_attribute(cx: &mut AcceptContext<'_, '_>, attribute: Symbol, span: Span) -> bool {
46 if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains(&attribute) {
48 return true;
49 }
50 cx.emit_err(DocAttributeNotAttribute { span, attribute });
51 false
52}
53
54fn 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 true
65}
66
67fn 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 true
74}
75
76fn expected_name_value(cx: &mut AcceptContext<'_, '_>, span: Span, _name: Option<Symbol>) {
78 cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNameValue, span);
79}
80
81fn expected_no_args(cx: &mut AcceptContext<'_, '_>, span: Span) {
83 cx.emit_lint(INVALID_DOC_ATTRIBUTES, ExpectedNoArgs, span);
84}
85
86fn 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}
95
96fn 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 };
107
108 let Some(value) = nv.value_as_str() else {
109 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
110 return;
111 };
112
113 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 }
121
122 let span = path.span();
123 if attr_value.is_some() {
124 cx.adcx().duplicate_key(span, path.word_sym().unwrap());
125 return;
126 }
127
128 if !check_attr_not_crate_level(cx, span, attr_name) {
129 return;
130 }
131
132 *attr_value = Some((value, span));
133}
134
135#[derive(#[automatically_derived]
impl ::core::default::Default for DocParser {
#[inline]
fn default() -> DocParser {
DocParser {
attribute: ::core::default::Default::default(),
nb_doc_attrs: ::core::default::Default::default(),
}
}
}Default, #[automatically_derived]
impl ::core::fmt::Debug for DocParser {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "DocParser",
"attribute", &self.attribute, "nb_doc_attrs", &&self.nb_doc_attrs)
}
}Debug)]
136pub(crate) struct DocParser {
137 attribute: DocAttribute,
138 nb_doc_attrs: usize,
139}
140
141impl 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();
149
150 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 }
156
157 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 }
166
167 if !check_attr_crate_level(cx, path.span()) {
168 return;
169 }
170
171 self.attribute.no_crate_inject = Some(path.span())
172 }
173 Some(sym::attr) => {
174 let Some(list) = args.as_list() else {
175 let span = cx.attr_span;
178 cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, span);
179 return;
180 };
181
182 for attr in list.mixed() {
184 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 }
197
198 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 }
204
205 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 }
219
220 if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
221 cx.emit_lint(UNUSED_ATTRIBUTES, DocAliasDuplicated { first_definition }, span);
222 }
223
224 self.attribute.aliases.insert(alias, span);
225 }
226
227 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 };
242
243 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 }
254
255 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 }
266
267 self.attribute.inline.push((inline, path.span()));
268 }
269
270 fn parse_cfg(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
271 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 }
289
290 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();
298
299 let mut values_set = FxHashSet::default();
300 for item in list.mixed() {
301 match item {
302 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 MetaItemOrLitParser::Lit(lit) => cx.emit_lint(
323 INVALID_DOC_ATTRIBUTES,
324 DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol },
325 lit.span,
326 ),
327 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() == 0
337 {
338 if ident.name == sym::any {
339 if let DocCfgHideShow::List(values) = &cfg_values
340 && 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 }
365
366 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 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 };
408
409 let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() };
410
411 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 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 ArgParser::NameValue(_) => {
448 cx.emit_lint(
449 INVALID_DOC_ATTRIBUTES,
450 DocAutoCfgHideShowUnexpectedItem { attr_name },
451 sub_item.span(),
452 );
453 }
454 _ => {
456 cx.emit_lint(
457 INVALID_DOC_ATTRIBUTES,
458 DocAutoCfgHideShowUnexpectedItemAfterValues,
459 sub_item.span(),
460 );
461 }
462 }
463 }
464
465 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 }
490
491 fn parse_single_doc_attr_item(&mut self, cx: &mut AcceptContext<'_, '_>, mip: &MetaItemParser) {
492 let path = mip.path();
493 let args = mip.args();
494
495 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 }
501
502 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_validation
541 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 };
550
551 let Some(s) = nv.value_as_str() else {
552 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
553 return;
554 };
555
556 if !check_attr_crate_level(cx, path.span()) {
557 return;
558 }
559
560 self.attribute.$ident = Some((s, path.span()));
570 }};
571 }
572
573 match path.word_sym() {
574 Some(sym::alias) => self.parse_alias(cx, path, args),
575 Some(sym::hidden) => {
if let Err(span) = args.as_no_args() {
expected_no_args(cx, span);
return;
}
self.attribute.hidden = Some(path.span());
}no_args!(hidden),
576 Some(sym::html_favicon_url) => {
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; }
self.attribute.html_favicon_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_favicon_url),
577 Some(sym::html_logo_url) => {
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; }
self.attribute.html_logo_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_logo_url),
578 Some(sym::html_no_source) => {
{
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; }
{}
self.attribute.html_no_source = Some(span);
};
}no_args_and_crate_level!(html_no_source),
579 Some(sym::html_playground_url) => {
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; }
self.attribute.html_playground_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_playground_url),
580 Some(sym::html_root_url) => {
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; }
self.attribute.html_root_url = Some((s, path.span()));
}string_arg_and_crate_level!(html_root_url),
581 Some(sym::issue_tracker_base_url) => {
582 {
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; }
self.attribute.issue_tracker_base_url = Some((s, path.span()));
}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) => {
if let Err(span) = args.as_no_args() {
expected_no_args(cx, span);
return;
}
self.attribute.masked = Some(path.span());
}no_args!(masked),
587 Some(sym::cfg) => self.parse_cfg(cx, args),
588 Some(sym::notable_trait) => {
if let Err(span) = args.as_no_args() {
expected_no_args(cx, span);
return;
}
self.attribute.notable_trait = Some(path.span());
}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) => {
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::fake_variadic) { return; }
self.attribute.fake_variadic = Some(span);
}no_args_and_not_crate_level!(fake_variadic),
604 Some(sym::search_unbox) => {
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::search_unbox) { return; }
self.attribute.search_unbox = Some(span);
}no_args_and_not_crate_level!(search_unbox),
605 Some(sym::rust_logo) => {
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; }
{
if !cx.features().rustdoc_internals() {
feature_err(cx.sess(), sym::rustdoc_internals, span,
rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("the `#[doc(rust_logo)]` attribute is used for Rust branding"))).emit();
}
}
self.attribute.rust_logo = Some(span);
}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 };
626
627 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 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 }
681
682 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 {
::core::panicking::panic_fmt(format_args!("internal error: entered unreachable code: {0}",
format_args!("Should have been handled at the same time as sugar-syntaxed doc comments")));
};unreachable!(
714 "Should have been handled at the same time as sugar-syntaxed doc comments"
715 );
716 }
717 }
718 }
719 }
720}
721
722impl AttributeParser for DocParser {
723 const ATTRIBUTES: AcceptMapping<Self> = &[(
724 &[sym::doc],
725 crate::AttributeTemplate {
word: false,
list: Some(&["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"]),
one_of: &[],
name_value_str: Some(&["string"]),
docs: None,
}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, |this, cx, args| {
757 this.accept_single_doc_attr(cx, args);
758 },
759 )];
760 const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(ALL_TARGETS);
762 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 None
803 }
804 }
805}