1use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
2use rustc_data_structures::fx::{FxHashSet, FxIndexMap, IndexEntry};
3use rustc_errors::{Applicability, msg};
4use rustc_feature::AttributeStability;
5use rustc_hir::Target;
6use rustc_hir::attrs::{
7 AttributeKind, CfgEntry, CfgHideShow, DocAttribute, DocCfgHideShow, DocCfgHideShowValue,
8 DocInline, HideOrShow,
9};
10use rustc_session::errors::feature_err;
11use rustc_span::{Span, Symbol, edition, sym};
12
13use super::prelude::{ALL_TARGETS, AllowedTargets};
14use super::{AcceptMapping, AttributeParser, template};
15use crate::context::{AcceptContext, FinalizeContext};
16use crate::diagnostics::{
17 AttrCrateLevelOnly, DocAliasDuplicated, DocAutoCfgExpectsHideOrShow,
18 DocAutoCfgHideShowExpectsList, DocAutoCfgHideShowNoIdentBeforeValues,
19 DocAutoCfgHideShowUnexpectedItem, DocAutoCfgHideShowUnexpectedItemAfterValues,
20 DocAutoCfgHideShowValuesMix, DocAutoCfgWrongLiteral, DocTestLiteral, DocTestTakesList,
21 DocTestUnknown, DocUnknownAny, DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins,
22 DocUnknownSpotlight, ExpectedNameValue, ExpectedNoArgs, IllFormedAttributeInput, MalformedDoc,
23};
24use crate::parser::{
25 ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser, OwnedPathParser,
26};
27use crate::session_diagnostics::{
28 DocAliasBadChar, DocAliasEmpty, DocAliasMalformed, DocAliasStartEnd, DocAttrNotCrateLevel,
29 DocAttributeNotAttribute, DocKeywordNotKeyword, UnusedDuplicate,
30};
31
32fn check_keyword(cx: &mut AcceptContext<'_, '_>, keyword: Symbol, span: Span) -> bool {
33 if keyword.is_reserved(|| edition::LATEST_STABLE_EDITION)
37 || keyword.is_weak()
38 || keyword == sym::SelfTy
39 {
40 return true;
41 }
42 cx.emit_err(DocKeywordNotKeyword { span, keyword });
43 false
44}
45
46fn check_attribute(cx: &mut AcceptContext<'_, '_>, attribute: Symbol, span: Span) -> bool {
47 if rustc_feature::BUILTIN_ATTRIBUTE_MAP.contains(&attribute) {
49 return true;
50 }
51 cx.emit_err(DocAttributeNotAttribute { span, attribute });
52 false
53}
54
55fn check_attr_not_crate_level(
57 cx: &mut AcceptContext<'_, '_>,
58 span: Span,
59 attr_name: Symbol,
60) -> bool {
61 if cx.shared.target == Target::Crate {
62 cx.emit_err(DocAttrNotCrateLevel { span, attr_name });
63 return false;
64 }
65 true
66}
67
68fn check_attr_crate_level(cx: &mut AcceptContext<'_, '_>, span: Span) -> bool {
70 if cx.shared.target != Target::Crate {
71 cx.emit_lint(
72 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
73 AttrCrateLevelOnly,
74 span,
75 );
76 return false;
77 }
78 true
79}
80
81fn expected_name_value(cx: &mut AcceptContext<'_, '_>, span: Span, _name: Option<Symbol>) {
83 cx.emit_lint(rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, ExpectedNameValue, span);
84}
85
86fn expected_no_args(cx: &mut AcceptContext<'_, '_>, span: Span) {
88 cx.emit_lint(rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, ExpectedNoArgs, span);
89}
90
91fn expected_string_literal(
94 cx: &mut AcceptContext<'_, '_>,
95 span: Span,
96 _actual_literal: Option<&MetaItemLit>,
97) {
98 cx.emit_lint(rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES, MalformedDoc, span);
99}
100
101fn parse_keyword_and_attribute(
102 cx: &mut AcceptContext<'_, '_>,
103 path: &OwnedPathParser,
104 args: &ArgParser,
105 attr_value: &mut Option<(Symbol, Span)>,
106 attr_name: Symbol,
107) {
108 let Some(nv) = args.as_name_value() else {
109 expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
110 return;
111 };
112
113 let Some(value) = nv.value_as_str() else {
114 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
115 return;
116 };
117
118 let ret = if attr_name == sym::keyword {
119 check_keyword(cx, value, nv.value_span)
120 } else {
121 check_attribute(cx, value, nv.value_span)
122 };
123 if !ret {
124 return;
125 }
126
127 let span = path.span();
128 if attr_value.is_some() {
129 cx.adcx().duplicate_key(span, path.word_sym().unwrap());
130 return;
131 }
132
133 if !check_attr_not_crate_level(cx, span, attr_name) {
134 return;
135 }
136
137 *attr_value = Some((value, span));
138}
139
140#[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)]
141pub(crate) struct DocParser {
142 attribute: DocAttribute,
143 nb_doc_attrs: usize,
144}
145
146impl DocParser {
147 fn parse_single_test_doc_attr_item(
148 &mut self,
149 cx: &mut AcceptContext<'_, '_>,
150 mip: &MetaItemParser,
151 ) {
152 let path = mip.path();
153 let args = mip.args();
154
155 match path.word_sym() {
156 Some(sym::no_crate_inject) => {
157 if let Err(span) = args.as_no_args() {
158 expected_no_args(cx, span);
159 return;
160 }
161
162 if let Some(used_span) = self.attribute.no_crate_inject {
163 let unused_span = path.span();
164 cx.emit_lint(
165 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
166 UnusedDuplicate { this: unused_span, other: used_span, warning: true },
167 unused_span,
168 );
169 return;
170 }
171
172 if !check_attr_crate_level(cx, path.span()) {
173 return;
174 }
175
176 self.attribute.no_crate_inject = Some(path.span())
177 }
178 Some(sym::attr) => {
179 let Some(list) = args.as_list() else {
180 let span = cx.attr_span;
183 cx.emit_lint(
184 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
185 MalformedDoc,
186 span,
187 );
188 return;
189 };
190
191 for attr in list.mixed() {
193 attr.ignore_args();
195 self.attribute.test_attrs.push(attr.span());
196 }
197 }
198 Some(name) => {
199 cx.emit_lint(
200 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
201 DocTestUnknown { name },
202 path.span(),
203 );
204 }
205 None => {
206 cx.emit_lint(
207 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
208 DocTestLiteral,
209 path.span(),
210 );
211 }
212 }
213 }
214
215 fn add_alias(&mut self, cx: &mut AcceptContext<'_, '_>, alias: Symbol, span: Span) {
216 let attr_str = "`#[doc(alias = \"...\")]`";
217 if alias == sym::empty {
218 cx.emit_err(DocAliasEmpty { span, attr_str });
219 return;
220 }
221
222 let alias_str = alias.as_str();
223 if let Some(c) =
224 alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
225 {
226 cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });
227 return;
228 }
229 if alias_str.starts_with(' ') || alias_str.ends_with(' ') {
230 cx.emit_err(DocAliasStartEnd { span, attr_str });
231 return;
232 }
233 if !check_attr_not_crate_level(cx, span, sym::alias) {
234 return;
235 }
236
237 if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
238 cx.emit_lint(
239 rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
240 DocAliasDuplicated { first_definition },
241 span,
242 );
243 }
244
245 self.attribute.aliases.insert(alias, span);
246 }
247
248 fn parse_alias(
249 &mut self,
250 cx: &mut AcceptContext<'_, '_>,
251 path: &OwnedPathParser,
252 args: &ArgParser,
253 ) {
254 match args {
255 ArgParser::NoArgs => {
256 cx.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) });
257 }
258 ArgParser::List(list) => {
259 for i in list.mixed() {
260 let Some(alias) = cx.expect_string_literal(i) else {
261 continue;
262 };
263
264 self.add_alias(cx, alias, i.span());
265 }
266 }
267 ArgParser::NameValue(nv) => {
268 let Some(alias) = cx.expect_string_literal(nv) else {
269 return;
270 };
271 self.add_alias(cx, alias, nv.value_span);
272 }
273 }
274 }
275
276 fn parse_inline(
277 &mut self,
278 cx: &mut AcceptContext<'_, '_>,
279 path: &OwnedPathParser,
280 args: &ArgParser,
281 inline: DocInline,
282 ) {
283 if let Err(span) = args.as_no_args() {
284 expected_no_args(cx, span);
285 return;
286 }
287
288 self.attribute.inline.push((inline, path.span()));
289 }
290
291 fn parse_cfg(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
292 fn simplify_cfg(cfg_entry: &mut CfgEntry) {
294 match cfg_entry {
295 CfgEntry::All(cfgs, span) if cfgs.is_empty() => {
296 *cfg_entry = CfgEntry::Bool(true, *span)
297 }
298 CfgEntry::Any(cfgs, span) if cfgs.is_empty() => {
299 *cfg_entry = CfgEntry::Bool(false, *span)
300 }
301 CfgEntry::Not(cfg, _) => simplify_cfg(cfg),
302 _ => {}
303 }
304 }
305 if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) {
306 simplify_cfg(&mut cfg_entry);
307 self.attribute.cfg.push(cfg_entry);
308 }
309 }
310
311 fn parse_auto_cfg_values(
313 &self,
314 cx: &mut AcceptContext<'_, '_>,
315 list: &MetaItemListParser,
316 values: &mut Option<DocCfgHideShow>,
317 ) {
318 let mut cfg_values = DocCfgHideShow::new();
319
320 let mut values_set = FxHashSet::default();
321 for item in list.mixed() {
322 match item {
323 MetaItemOrLitParser::Lit(MetaItemLit {
325 kind: LitKind::Str(symbol, _),
326 span,
327 ..
328 }) => match &mut cfg_values {
329 DocCfgHideShow::Any(any_span) => {
330 cx.emit_lint(
331 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
332 DocAutoCfgHideShowValuesMix { value_span: *span },
333 *any_span,
334 );
335 }
336 DocCfgHideShow::List(symbols) => {
337 if values_set.insert(symbol) {
338 symbols.push(DocCfgHideShowValue::new(*symbol, *span));
339 }
340 }
341 },
342 MetaItemOrLitParser::Lit(lit) => cx.emit_lint(
344 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
345 DocAutoCfgHideShowUnexpectedItem { attr_name: lit.symbol },
346 lit.span,
347 ),
348 MetaItemOrLitParser::MetaItemParser(sub_item) => {
351 if let Some(ident) = sub_item.ident()
352 && [sym::any, sym::none].contains(&ident.name)
353 && let ArgParser::List(list) = sub_item.args()
354 && list.mixed().count() == 0
355 {
356 if ident.name == sym::any {
357 if let DocCfgHideShow::List(values) = &cfg_values
358 && let Some(value) = values.first()
359 {
360 cx.emit_lint(
361 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
362 DocAutoCfgHideShowValuesMix { value_span: value.span },
363 sub_item.span(),
364 );
365 } else {
366 cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span()));
367 }
368 } else {
369 cfg_values.push_none(sub_item.span());
370 }
371 } else {
372 cx.emit_lint(
373 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
374 DocAutoCfgHideShowUnexpectedItem {
375 attr_name: sub_item.ident().unwrap().name,
376 },
377 sub_item.span(),
378 );
379 }
380 }
381 }
382 }
383 *values = Some(cfg_values);
384 }
385
386 fn parse_auto_cfg(
387 &mut self,
388 cx: &mut AcceptContext<'_, '_>,
389 path: &OwnedPathParser,
390 args: &ArgParser,
391 ) {
392 match args {
393 ArgParser::NoArgs => {
394 self.attribute.auto_cfg_change.push((true, path.span()));
395 }
396 ArgParser::List(list) => {
397 'main: for meta in list.mixed() {
398 let MetaItemOrLitParser::MetaItemParser(item) = meta else {
399 cx.emit_lint(
400 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
401 DocAutoCfgExpectsHideOrShow,
402 meta.span(),
403 );
404 continue;
405 };
406 let (kind, attr_name) = match item.path().word_sym() {
409 Some(sym::hide) => (HideOrShow::Hide, sym::hide),
410 Some(sym::show) => (HideOrShow::Show, sym::show),
411 _ => {
412 cx.emit_lint(
413 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
414 DocAutoCfgExpectsHideOrShow,
415 item.span(),
416 );
417 continue;
418 }
419 };
420 let ArgParser::List(list) = item.args() else {
421 cx.emit_lint(
422 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
423 DocAutoCfgHideShowExpectsList { attr_name },
424 item.span(),
425 );
426 continue;
427 };
428
429 let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() };
430
431 let mut cfg_names = FxHashSet::default();
432 let mut values = None;
433 for item in list.mixed() {
434 let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
435 cx.emit_lint(
436 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
437 DocAutoCfgHideShowUnexpectedItem { attr_name },
438 item.span(),
439 );
440 continue 'main;
441 };
442 match sub_item.args() {
443 ArgParser::NoArgs if values.is_none() => {
444 let Some(name) = sub_item.path().word_sym() else {
445 cx.adcx().expected_identifier(sub_item.path().span());
446 continue 'main;
447 };
448 cfg_names.insert(name);
449 }
450 ArgParser::List(list) if values.is_none() => {
452 let Some(sym::values) = sub_item.path().word_sym() else {
453 cx.adcx().expected_identifier(sub_item.path().span());
454 continue 'main;
455 };
456 if cfg_names.is_empty() {
457 cx.emit_lint(
458 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
459 DocAutoCfgHideShowNoIdentBeforeValues,
460 sub_item.span(),
461 );
462 continue 'main;
463 }
464 self.parse_auto_cfg_values(cx, list, &mut values);
465 }
466 ArgParser::NameValue(_) => {
468 cx.emit_lint(
469 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
470 DocAutoCfgHideShowUnexpectedItem { attr_name },
471 sub_item.span(),
472 );
473 }
474 _ => {
476 cx.emit_lint(
477 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
478 DocAutoCfgHideShowUnexpectedItemAfterValues,
479 sub_item.span(),
480 );
481 }
482 }
483 }
484
485 let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span()));
486 #[allow(rustc::potential_query_instability)]
487 for cfg_name in &cfg_names {
488 match cfg_hide_show.values.entry(*cfg_name) {
489 IndexEntry::Vacant(v) => {
490 v.insert(values.clone());
491 }
492 IndexEntry::Occupied(mut o) => {
493 o.get_mut().merge_with(&values);
494 }
495 }
496 }
497 self.attribute.auto_cfg.push((cfg_hide_show, path.span()));
498 }
499 }
500 ArgParser::NameValue(nv) => {
501 let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()
502 else {
503 cx.emit_lint(
504 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
505 DocAutoCfgWrongLiteral,
506 nv.value_span,
507 );
508 return;
509 };
510 self.attribute.auto_cfg_change.push((*bool_value, *span));
511 }
512 }
513 }
514
515 fn parse_single_doc_attr_item(&mut self, cx: &mut AcceptContext<'_, '_>, mip: &MetaItemParser) {
516 let path = mip.path();
517 let args = mip.args();
518
519 macro_rules! no_args {
520 ($ident: ident) => {{
521 if let Err(span) = args.as_no_args() {
522 expected_no_args(cx, span);
523 return;
524 }
525
526 self.attribute.$ident = Some(path.span());
536 }};
537 }
538 macro_rules! no_args_and_not_crate_level {
539 ($ident: ident) => {{
540 if let Err(span) = args.as_no_args() {
541 expected_no_args(cx, span);
542 return;
543 }
544 let span = path.span();
545 if !check_attr_not_crate_level(cx, span, sym::$ident) {
546 return;
547 }
548 self.attribute.$ident = Some(span);
549 }};
550 }
551 macro_rules! no_args_and_crate_level {
552 ($ident: ident) => {{
553 no_args_and_crate_level!($ident, |span| {});
554 }};
555 ($ident: ident, |$span:ident| $extra_validation:block) => {{
556 if let Err(span) = args.as_no_args() {
557 expected_no_args(cx, span);
558 return;
559 }
560 let $span = path.span();
561 if !check_attr_crate_level(cx, $span) {
562 return;
563 }
564 $extra_validation
565 self.attribute.$ident = Some($span);
566 }};
567 }
568 macro_rules! string_arg_and_crate_level {
569 ($ident: ident) => {{
570 let Some(nv) = args.as_name_value() else {
571 expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
572 return;
573 };
574
575 let Some(s) = nv.value_as_str() else {
576 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
577 return;
578 };
579
580 if !check_attr_crate_level(cx, path.span()) {
581 return;
582 }
583
584 self.attribute.$ident = Some((s, path.span()));
594 }};
595 }
596
597 match path.word_sym() {
598 Some(sym::alias) => self.parse_alias(cx, path, args),
599 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),
600 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),
601 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),
602 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),
603 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),
604 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),
605 Some(sym::issue_tracker_base_url) => {
606 {
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)
607 }
608 Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
609 Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
610 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),
611 Some(sym::cfg) => self.parse_cfg(cx, args),
612 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),
613 Some(sym::keyword) => parse_keyword_and_attribute(
614 cx,
615 path,
616 args,
617 &mut self.attribute.keyword,
618 sym::keyword,
619 ),
620 Some(sym::attribute) => parse_keyword_and_attribute(
621 cx,
622 path,
623 args,
624 &mut self.attribute.attribute,
625 sym::attribute,
626 ),
627 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),
628 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),
629 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| {
630 if !cx.features().rustdoc_internals() {
631 feature_err(
632 cx.sess(),
633 sym::rustdoc_internals,
634 span,
635 msg!("the `#[doc(rust_logo)]` attribute is used for Rust branding"),
636 )
637 .emit();
638 }
639 }),
640 Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args),
641 Some(sym::test) => {
642 let Some(list) = args.as_list() else {
643 cx.emit_lint(
644 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
645 DocTestTakesList,
646 args.span().unwrap_or(path.span()),
647 );
648 return;
649 };
650
651 for i in list.mixed() {
652 match i {
653 MetaItemOrLitParser::MetaItemParser(mip) => {
654 self.parse_single_test_doc_attr_item(cx, mip);
655 }
656 MetaItemOrLitParser::Lit(lit) => {
657 cx.emit_lint(
661 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
662 MalformedDoc,
663 lit.span,
664 );
665 }
666 }
667 }
668 }
669 Some(sym::spotlight) => {
670 let span = path.span();
671 cx.emit_lint(
672 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
673 DocUnknownSpotlight { sugg_span: span },
674 span,
675 );
676 }
677 Some(sym::include) if let Some(nv) = args.as_name_value() => {
678 let inner = match cx.attr_style {
679 AttrStyle::Outer => "",
680 AttrStyle::Inner => "!",
681 };
682 let value = nv.value_as_lit().symbol;
683 let span = path.span();
684 cx.emit_lint(
685 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
686 DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) },
687 span,
688 );
689 }
690 Some(name @ (sym::passes | sym::no_default_passes)) => {
691 let span = path.span();
692 cx.emit_lint(
693 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
694 DocUnknownPasses { name, note_span: span },
695 span,
696 );
697 }
698 Some(sym::plugins) => {
699 let span = path.span();
700 cx.emit_lint(
701 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
702 DocUnknownPlugins { label_span: span },
703 span,
704 );
705 }
706 Some(name) => {
707 cx.emit_lint(
708 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
709 DocUnknownAny { name },
710 path.span(),
711 );
712 }
713 None => {
714 let full_name =
715 path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();
716 let name = Symbol::intern(&full_name);
717 cx.emit_lint(
718 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
719 DocUnknownAny { name },
720 path.span(),
721 );
722 }
723 }
724 }
725
726 fn accept_single_doc_attr(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
727 match args {
728 ArgParser::NoArgs => {
729 let suggestions = cx.adcx().suggestions();
730 let span = cx.attr_span;
731 cx.emit_lint(
732 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
733 IllFormedAttributeInput::new(&suggestions, None, None),
734 span,
735 );
736 }
737 ArgParser::List(items) => {
738 for i in items.mixed() {
739 match i {
740 MetaItemOrLitParser::MetaItemParser(mip) => {
741 if self.nb_doc_attrs == 0 {
742 self.attribute.first_span = cx.attr_span;
743 }
744 self.nb_doc_attrs += 1;
745 self.parse_single_doc_attr_item(cx, mip);
746 }
747 MetaItemOrLitParser::Lit(lit) => {
748 expected_name_value(cx, lit.span, None);
749 }
750 }
751 }
752 }
753 ArgParser::NameValue(nv) => {
754 if nv.value_as_str().is_none() {
755 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
756 } else {
757 {
::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!(
758 "Should have been handled at the same time as sugar-syntaxed doc comments"
759 );
760 }
761 }
762 }
763 }
764}
765
766impl AttributeParser for DocParser {
767 const ATTRIBUTES: AcceptMapping<Self> = &[(
768 &[sym::doc],
769 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!(
770 List: &[
771 "alias",
772 "attribute",
773 "hidden",
774 "html_favicon_url",
775 "html_logo_url",
776 "html_no_source",
777 "html_playground_url",
778 "html_root_url",
779 "issue_tracker_base_url",
780 "inline",
781 "no_inline",
782 "masked",
783 "cfg",
784 "notable_trait",
785 "keyword",
786 "fake_variadic",
787 "search_unbox",
788 "rust_logo",
789 "auto_cfg",
790 "test",
791 "spotlight",
792 "include",
793 "no_default_passes",
794 "passes",
795 "plugins",
796 ],
797 NameValueStr: "string"
798 ),
799 AttributeStability::Stable, |this, cx, args| {
801 this.accept_single_doc_attr(cx, args);
802 },
803 )];
804 const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(ALL_TARGETS);
806 fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
839 if self.nb_doc_attrs != 0 {
840 Some(AttributeKind::Doc(Box::new(self.attribute)))
841 } else {
842 None
843 }
844 }
845}