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