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