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::diagnostics::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 let Some(ident) = sub_item.ident() else {
352 cx.adcx().expected_identifier(sub_item.path().span());
353 continue;
354 };
355 if [sym::any, sym::none].contains(&ident.name)
356 && let ArgParser::List(list) = sub_item.args()
357 && list.mixed().count() == 0
358 {
359 if ident.name == sym::any {
360 if let DocCfgHideShow::List(values) = &cfg_values
361 && let Some(value) = values.first()
362 {
363 cx.emit_lint(
364 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
365 DocAutoCfgHideShowValuesMix { value_span: value.span },
366 sub_item.span(),
367 );
368 } else {
369 cfg_values.merge_with(&DocCfgHideShow::Any(sub_item.span()));
370 }
371 } else {
372 cfg_values.push_none(sub_item.span());
373 }
374 } else {
375 cx.emit_lint(
376 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
377 DocAutoCfgHideShowUnexpectedItem { attr_name: ident.name },
378 sub_item.span(),
379 );
380 }
381 }
382 }
383 }
384 *values = Some(cfg_values);
385 }
386
387 fn parse_auto_cfg(
388 &mut self,
389 cx: &mut AcceptContext<'_, '_>,
390 path: &OwnedPathParser,
391 args: &ArgParser,
392 ) {
393 match args {
394 ArgParser::NoArgs => {
395 self.attribute.auto_cfg_change.push((true, path.span()));
396 }
397 ArgParser::List(list) => {
398 'main: for meta in list.mixed() {
399 let MetaItemOrLitParser::MetaItemParser(item) = meta else {
400 cx.emit_lint(
401 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
402 DocAutoCfgExpectsHideOrShow,
403 meta.span(),
404 );
405 continue;
406 };
407 let (kind, attr_name) = match item.path().word_sym() {
410 Some(sym::hide) => (HideOrShow::Hide, sym::hide),
411 Some(sym::show) => (HideOrShow::Show, sym::show),
412 _ => {
413 cx.emit_lint(
414 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
415 DocAutoCfgExpectsHideOrShow,
416 item.span(),
417 );
418 continue;
419 }
420 };
421 let ArgParser::List(list) = item.args() else {
422 cx.emit_lint(
423 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
424 DocAutoCfgHideShowExpectsList { attr_name },
425 item.span(),
426 );
427 continue;
428 };
429
430 let mut cfg_hide_show = CfgHideShow { kind, values: FxIndexMap::default() };
431
432 let mut cfg_names = FxHashSet::default();
433 let mut values = None;
434 for item in list.mixed() {
435 let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
436 cx.emit_lint(
437 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
438 DocAutoCfgHideShowUnexpectedItem { attr_name },
439 item.span(),
440 );
441 continue 'main;
442 };
443 match sub_item.args() {
444 ArgParser::NoArgs if values.is_none() => {
445 let Some(name) = sub_item.path().word_sym() else {
446 cx.adcx().expected_identifier(sub_item.path().span());
447 continue 'main;
448 };
449 cfg_names.insert(name);
450 }
451 ArgParser::List(list) if values.is_none() => {
453 let Some(sym::values) = sub_item.path().word_sym() else {
454 cx.adcx().expected_identifier(sub_item.path().span());
455 continue 'main;
456 };
457 if cfg_names.is_empty() {
458 cx.emit_lint(
459 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
460 DocAutoCfgHideShowNoIdentBeforeValues,
461 sub_item.span(),
462 );
463 continue 'main;
464 }
465 self.parse_auto_cfg_values(cx, list, &mut values);
466 }
467 ArgParser::NameValue(_) => {
469 cx.emit_lint(
470 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
471 DocAutoCfgHideShowUnexpectedItem { attr_name },
472 sub_item.span(),
473 );
474 }
475 _ => {
477 cx.emit_lint(
478 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
479 DocAutoCfgHideShowUnexpectedItemAfterValues,
480 sub_item.span(),
481 );
482 }
483 }
484 }
485
486 let values = values.unwrap_or(DocCfgHideShow::new_with_only_key(item.span()));
487 #[allow(rustc::potential_query_instability)]
488 for cfg_name in &cfg_names {
489 match cfg_hide_show.values.entry(*cfg_name) {
490 IndexEntry::Vacant(v) => {
491 v.insert(values.clone());
492 }
493 IndexEntry::Occupied(mut o) => {
494 o.get_mut().merge_with(&values);
495 }
496 }
497 }
498 self.attribute.auto_cfg.push((cfg_hide_show, path.span()));
499 }
500 }
501 ArgParser::NameValue(nv) => {
502 let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()
503 else {
504 cx.emit_lint(
505 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
506 DocAutoCfgWrongLiteral,
507 nv.value_span,
508 );
509 return;
510 };
511 self.attribute.auto_cfg_change.push((*bool_value, *span));
512 }
513 }
514 }
515
516 fn parse_single_doc_attr_item(&mut self, cx: &mut AcceptContext<'_, '_>, mip: &MetaItemParser) {
517 let path = mip.path();
518 let args = mip.args();
519
520 macro_rules! no_args {
521 ($ident: ident) => {{
522 if let Err(span) = args.as_no_args() {
523 expected_no_args(cx, span);
524 return;
525 }
526
527 self.attribute.$ident = Some(path.span());
537 }};
538 }
539 macro_rules! no_args_and_not_crate_level {
540 ($ident: ident) => {{
541 if let Err(span) = args.as_no_args() {
542 expected_no_args(cx, span);
543 return;
544 }
545 let span = path.span();
546 if !check_attr_not_crate_level(cx, span, sym::$ident) {
547 return;
548 }
549 self.attribute.$ident = Some(span);
550 }};
551 }
552 macro_rules! no_args_and_crate_level {
553 ($ident: ident) => {{
554 no_args_and_crate_level!($ident, |span| {});
555 }};
556 ($ident: ident, |$span:ident| $extra_validation:block) => {{
557 if let Err(span) = args.as_no_args() {
558 expected_no_args(cx, span);
559 return;
560 }
561 let $span = path.span();
562 if !check_attr_crate_level(cx, $span) {
563 return;
564 }
565 $extra_validation
566 self.attribute.$ident = Some($span);
567 }};
568 }
569 macro_rules! string_arg_and_crate_level {
570 ($ident: ident) => {{
571 let Some(nv) = args.as_name_value() else {
572 expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
573 return;
574 };
575
576 let Some(s) = nv.value_as_str() else {
577 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
578 return;
579 };
580
581 if !check_attr_crate_level(cx, path.span()) {
582 return;
583 }
584
585 self.attribute.$ident = Some((s, path.span()));
595 }};
596 }
597
598 match path.word_sym() {
599 Some(sym::alias) => self.parse_alias(cx, path, args),
600 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),
601 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),
602 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),
603 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),
604 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),
605 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),
606 Some(sym::issue_tracker_base_url) => {
607 {
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)
608 }
609 Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
610 Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
611 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),
612 Some(sym::cfg) => self.parse_cfg(cx, args),
613 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),
614 Some(sym::keyword) => parse_keyword_and_attribute(
615 cx,
616 path,
617 args,
618 &mut self.attribute.keyword,
619 sym::keyword,
620 ),
621 Some(sym::attribute) => parse_keyword_and_attribute(
622 cx,
623 path,
624 args,
625 &mut self.attribute.attribute,
626 sym::attribute,
627 ),
628 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),
629 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),
630 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| {
631 if !cx.features().rustdoc_internals() {
632 feature_err(
633 cx.sess(),
634 sym::rustdoc_internals,
635 span,
636 msg!("the `#[doc(rust_logo)]` attribute is used for Rust branding"),
637 )
638 .emit();
639 }
640 }),
641 Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args),
642 Some(sym::test) => {
643 let Some(list) = args.as_list() else {
644 cx.emit_lint(
645 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
646 DocTestTakesList,
647 args.span().unwrap_or(path.span()),
648 );
649 return;
650 };
651
652 for i in list.mixed() {
653 match i {
654 MetaItemOrLitParser::MetaItemParser(mip) => {
655 self.parse_single_test_doc_attr_item(cx, mip);
656 }
657 MetaItemOrLitParser::Lit(lit) => {
658 cx.emit_lint(
662 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
663 MalformedDoc,
664 lit.span,
665 );
666 }
667 }
668 }
669 }
670 Some(sym::spotlight) => {
671 let span = path.span();
672 cx.emit_lint(
673 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
674 DocUnknownSpotlight { sugg_span: span },
675 span,
676 );
677 }
678 Some(sym::include) if let Some(nv) = args.as_name_value() => {
679 let inner = match cx.attr_style {
680 AttrStyle::Outer => "",
681 AttrStyle::Inner => "!",
682 };
683 let value = nv.value_as_lit().symbol;
684 let span = path.span();
685 cx.emit_lint(
686 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
687 DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) },
688 span,
689 );
690 }
691 Some(name @ (sym::passes | sym::no_default_passes)) => {
692 let span = path.span();
693 cx.emit_lint(
694 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
695 DocUnknownPasses { name, note_span: span },
696 span,
697 );
698 }
699 Some(sym::plugins) => {
700 let span = path.span();
701 cx.emit_lint(
702 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
703 DocUnknownPlugins { label_span: span },
704 span,
705 );
706 }
707 Some(name) => {
708 cx.emit_lint(
709 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
710 DocUnknownAny { name },
711 path.span(),
712 );
713 }
714 None => {
715 let full_name =
716 path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();
717 let name = Symbol::intern(&full_name);
718 cx.emit_lint(
719 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
720 DocUnknownAny { name },
721 path.span(),
722 );
723 }
724 }
725 }
726
727 fn accept_single_doc_attr(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
728 match args {
729 ArgParser::NoArgs => {
730 let suggestions = cx.adcx().suggestions();
731 let span = cx.attr_span;
732 cx.emit_lint(
733 rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
734 IllFormedAttributeInput::new(&suggestions, None, None),
735 span,
736 );
737 }
738 ArgParser::List(items) => {
739 for i in items.mixed() {
740 match i {
741 MetaItemOrLitParser::MetaItemParser(mip) => {
742 if self.nb_doc_attrs == 0 {
743 self.attribute.first_span = cx.attr_span;
744 }
745 self.nb_doc_attrs += 1;
746 self.parse_single_doc_attr_item(cx, mip);
747 }
748 MetaItemOrLitParser::Lit(lit) => {
749 expected_name_value(cx, lit.span, None);
750 }
751 }
752 }
753 }
754 ArgParser::NameValue(nv) => {
755 if nv.value_as_str().is_none() {
756 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
757 } else {
758 {
::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!(
759 "Should have been handled at the same time as sugar-syntaxed doc comments"
760 );
761 }
762 }
763 }
764 }
765}
766
767impl AttributeParser for DocParser {
768 const ATTRIBUTES: AcceptMapping<Self> = &[(
769 &[sym::doc],
770 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!(
771 List: &[
772 "alias",
773 "attribute",
774 "hidden",
775 "html_favicon_url",
776 "html_logo_url",
777 "html_no_source",
778 "html_playground_url",
779 "html_root_url",
780 "issue_tracker_base_url",
781 "inline",
782 "no_inline",
783 "masked",
784 "cfg",
785 "notable_trait",
786 "keyword",
787 "fake_variadic",
788 "search_unbox",
789 "rust_logo",
790 "auto_cfg",
791 "test",
792 "spotlight",
793 "include",
794 "no_default_passes",
795 "passes",
796 "plugins",
797 ],
798 NameValueStr: "string"
799 ),
800 AttributeStability::Stable, |this, cx, args| {
802 this.accept_single_doc_attr(cx, args);
803 },
804 )];
805 const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(ALL_TARGETS);
807 fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
840 if self.nb_doc_attrs != 0 {
841 Some(AttributeKind::Doc(Box::new(self.attribute)))
842 } else {
843 None
844 }
845 }
846}