Skip to main content

rustc_attr_parsing/attributes/
doc.rs

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