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