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