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