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, MultiSpan, msg};
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, DocInlineConflict, DocInlineOnlyUse,
22 DocKeywordNotKeyword, DocTestLiteral, DocTestTakesList, DocTestUnknown, DocUnknownAny,
23 DocUnknownInclude, DocUnknownPasses, DocUnknownPlugins, DocUnknownSpotlight, ExpectedNameValue,
24 ExpectedNoArgs, 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() -> Self {
Self {
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 let span = path.span();
267 if let Some((previous_inline, previous_span)) = self.attribute.inline {
268 if previous_inline != inline {
269 let mut spans = MultiSpan::from_spans(::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[previous_span, span]))vec![previous_span, span]);
270 spans.push_span_label(previous_span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("this attribute..."))msg!("this attribute..."));
271 spans.push_span_label(span, rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed("{\".\"}..conflicts with this attribute"))msg!("{\".\"}..conflicts with this attribute"));
272 cx.emit_err(DocInlineConflict { spans });
273 }
274 } else {
275 self.attribute.inline = Some((inline, span));
276 }
277
278 match cx.target {
279 Target::Use | Target::ExternCrate => {}
280 _ => {
281 let item_span = cx.target_span;
282 cx.emit_lint(
283 INVALID_DOC_ATTRIBUTES,
284 DocInlineOnlyUse { attr_span: span, item_span },
285 span,
286 );
287 }
288 }
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 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 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 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 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 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 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 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 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 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 INVALID_DOC_ATTRIBUTES,
471 DocAutoCfgHideShowUnexpectedItem { attr_name },
472 sub_item.span(),
473 );
474 }
475 _ => {
477 cx.emit_lint(
478 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(INVALID_DOC_ATTRIBUTES, DocAutoCfgWrongLiteral, nv.value_span);
505 return;
506 };
507 self.attribute.auto_cfg_change.push((*bool_value, *span));
508 }
509 }
510 }
511
512 fn parse_single_doc_attr_item(&mut self, cx: &mut AcceptContext<'_, '_>, mip: &MetaItemParser) {
513 let path = mip.path();
514 let args = mip.args();
515
516 macro_rules! no_args {
517 ($ident: ident) => {{
518 if let Err(span) = args.as_no_args() {
519 expected_no_args(cx, span);
520 return;
521 }
522
523 self.attribute.$ident = Some(path.span());
533 }};
534 }
535 macro_rules! no_args_and_not_crate_level {
536 ($ident: ident) => {{
537 if let Err(span) = args.as_no_args() {
538 expected_no_args(cx, span);
539 return;
540 }
541 let span = path.span();
542 if !check_attr_not_crate_level(cx, span, sym::$ident) {
543 return;
544 }
545 self.attribute.$ident = Some(span);
546 }};
547 }
548 macro_rules! no_args_and_crate_level {
549 ($ident: ident) => {{
550 if let Err(span) = args.as_no_args() {
551 expected_no_args(cx, span);
552 return;
553 }
554 let span = path.span();
555 if !check_attr_crate_level(cx, span) {
556 return;
557 }
558 self.attribute.$ident = Some(span);
559 }};
560 }
561 macro_rules! string_arg_and_crate_level {
562 ($ident: ident) => {{
563 let Some(nv) = args.as_name_value() else {
564 expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
565 return;
566 };
567
568 let Some(s) = nv.value_as_str() else {
569 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
570 return;
571 };
572
573 if !check_attr_crate_level(cx, path.span()) {
574 return;
575 }
576
577 self.attribute.$ident = Some((s, path.span()));
587 }};
588 }
589 macro_rules! gated {
590 ($feature:ident $(,$notes:expr)*) => {
591 let stability = $crate::unstable!($feature $(, $notes)*);
592 cx.shared.cx.check_attribute_stability(&cx.attr_path, path.span(), stability);
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) => {
610 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);
611 {
if let Err(span) = args.as_no_args() {
expected_no_args(cx, span);
return;
}
self.attribute.masked = Some(path.span());
}no_args!(masked)
612 }
613 Some(sym::cfg) => {
614 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);
615 self.parse_cfg(cx, args)
616 }
617 Some(sym::notable_trait) => {
618 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);
619 {
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)
620 }
621 Some(sym::keyword) => {
622 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);
623 parse_keyword_and_attribute(
624 cx,
625 path,
626 args,
627 &mut self.attribute.keyword,
628 sym::keyword,
629 )
630 }
631 Some(sym::attribute) => {
632 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);
633 parse_keyword_and_attribute(
634 cx,
635 path,
636 args,
637 &mut self.attribute.attribute,
638 sym::attribute,
639 )
640 }
641 Some(sym::fake_variadic) => {
642 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);
643 {
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)
644 }
645 Some(sym::search_unbox) => {
646 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);
647 {
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)
648 }
649 Some(sym::rust_logo) => {
650 if cx.target == Target::Crate {
652 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!(
653 rustdoc_internals,
654 "the `#[doc(rust_logo)]` attribute is used for Rust branding"
655 );
656 }
657 {
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)
658 }
659 Some(sym::auto_cfg) => {
660 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);
661 self.parse_auto_cfg(cx, path, args)
662 }
663 Some(sym::test) => {
664 let Some(list) = args.as_list() else {
665 cx.emit_lint(
666 INVALID_DOC_ATTRIBUTES,
667 DocTestTakesList,
668 args.span().unwrap_or(path.span()),
669 );
670 return;
671 };
672
673 for i in list.mixed() {
674 match i {
675 MetaItemOrLitParser::MetaItemParser(mip) => {
676 self.parse_single_test_doc_attr_item(cx, mip);
677 }
678 MetaItemOrLitParser::Lit(lit) => {
679 cx.emit_lint(INVALID_DOC_ATTRIBUTES, MalformedDoc, lit.span);
683 }
684 }
685 }
686 }
687 Some(sym::spotlight) => {
688 let span = path.span();
689 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownSpotlight { sugg_span: span }, span);
690 }
691 Some(sym::include) if let Some(nv) = args.as_name_value() => {
692 let inner = match cx.attr_style {
693 AttrStyle::Outer => "",
694 AttrStyle::Inner => "!",
695 };
696 let value = nv.value_as_lit().symbol;
697 let span = path.span();
698 cx.emit_lint(
699 INVALID_DOC_ATTRIBUTES,
700 DocUnknownInclude { inner, value, sugg: (span, Applicability::MaybeIncorrect) },
701 span,
702 );
703 }
704 Some(name @ (sym::passes | sym::no_default_passes)) => {
705 let span = path.span();
706 cx.emit_lint(
707 INVALID_DOC_ATTRIBUTES,
708 DocUnknownPasses { name, note_span: span },
709 span,
710 );
711 }
712 Some(sym::plugins) => {
713 let span = path.span();
714 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownPlugins { label_span: span }, span);
715 }
716 Some(name) => {
717 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownAny { name }, path.span());
718 }
719 None => {
720 let full_name =
721 path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();
722 let name = Symbol::intern(&full_name);
723 cx.emit_lint(INVALID_DOC_ATTRIBUTES, DocUnknownAny { name }, path.span());
724 }
725 }
726 }
727
728 fn accept_single_doc_attr(&mut self, cx: &mut AcceptContext<'_, '_>, args: &ArgParser) {
729 match args {
730 ArgParser::List(items) if !items.is_empty() => {
731 for i in items.mixed() {
732 match i {
733 MetaItemOrLitParser::MetaItemParser(mip) => {
734 if self.nb_doc_attrs == 0 {
735 self.attribute.first_span = cx.attr_span;
736 }
737 self.nb_doc_attrs += 1;
738 self.parse_single_doc_attr_item(cx, mip);
739 }
740 MetaItemOrLitParser::Lit(lit) => {
741 expected_name_value(cx, lit.span, None);
742 }
743 }
744 }
745 }
746 ArgParser::NameValue(nv) => {
747 if nv.value_as_str().is_none() {
748 expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
749 } else {
750 {
::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!(
751 "Should have been handled at the same time as sugar-syntaxed doc comments"
752 );
753 }
754 }
755 _ => {
756 let suggestions = cx.adcx().suggestions();
757 let span = cx.inner_span;
758 cx.emit_lint(
759 INVALID_DOC_ATTRIBUTES,
760 IllFormedAttributeInput::new(&suggestions, None, None),
761 span,
762 );
763 }
764 }
765 }
766}
767
768impl AttributeParser for DocParser {
769 const ATTRIBUTES: AcceptMapping<Self> = &[(
770 &[sym::doc],
771 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!(
772 List: &[
773 "alias",
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 "auto_cfg",
787 "test",
788 ],
789 NameValueStr: "doc comment"
790 ),
791 AttributeStability::Stable, |this, cx, args| {
793 this.accept_single_doc_attr(cx, args);
794 },
795 )];
796 const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(ALL_TARGETS);
798 fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
835 if self.nb_doc_attrs != 0 {
836 Some(AttributeKind::Doc(Box::new(self.attribute)))
837 } else {
838 None
839 }
840 }
841}