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