Skip to main content

rustc_attr_parsing/attributes/
doc.rs

1use rustc_ast::ast::{AttrStyle, LitKind, MetaItemLit};
2use rustc_errors::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.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_lint(
175                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
176                        AttributeLintKind::UnusedDuplicate {
177                            this: unused_span,
178                            other: used_span,
179                            warning: true,
180                        },
181                        unused_span,
182                    );
183                    return;
184                }
185
186                if !check_attr_crate_level(cx, path.span()) {
187                    return;
188                }
189
190                self.attribute.no_crate_inject = Some(path.span())
191            }
192            Some(sym::attr) => {
193                let Some(list) = args.list() else {
194                    // FIXME: remove this method once merged and uncomment the line below instead.
195                    // cx.expected_list(cx.attr_span, args);
196                    let span = cx.attr_span;
197                    cx.emit_lint(
198                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
199                        AttributeLintKind::MalformedDoc,
200                        span,
201                    );
202                    return;
203                };
204
205                // FIXME: convert list into a Vec of `AttributeKind` because current code is awful.
206                for attr in list.mixed() {
207                    self.attribute.test_attrs.push(attr.span());
208                }
209            }
210            Some(name) => {
211                cx.emit_lint(
212                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
213                    AttributeLintKind::DocTestUnknown { name },
214                    path.span(),
215                );
216            }
217            None => {
218                cx.emit_lint(
219                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
220                    AttributeLintKind::DocTestLiteral,
221                    path.span(),
222                );
223            }
224        }
225    }
226
227    fn add_alias<S: Stage>(
228        &mut self,
229        cx: &mut AcceptContext<'_, '_, S>,
230        alias: Symbol,
231        span: Span,
232    ) {
233        let attr_str = "`#[doc(alias = \"...\")]`";
234        if alias == sym::empty {
235            cx.emit_err(DocAliasEmpty { span, attr_str });
236            return;
237        }
238
239        let alias_str = alias.as_str();
240        if let Some(c) =
241            alias_str.chars().find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
242        {
243            cx.emit_err(DocAliasBadChar { span, attr_str, char_: c });
244            return;
245        }
246        if alias_str.starts_with(' ') || alias_str.ends_with(' ') {
247            cx.emit_err(DocAliasStartEnd { span, attr_str });
248            return;
249        }
250        if !check_attr_not_crate_level(cx, span, sym::alias) {
251            return;
252        }
253
254        if let Some(first_definition) = self.attribute.aliases.get(&alias).copied() {
255            cx.emit_lint(
256                rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
257                AttributeLintKind::DuplicateDocAlias { first_definition },
258                span,
259            );
260        }
261
262        self.attribute.aliases.insert(alias, span);
263    }
264
265    fn parse_alias<S: Stage>(
266        &mut self,
267        cx: &mut AcceptContext<'_, '_, S>,
268        path: &OwnedPathParser,
269        args: &ArgParser,
270    ) {
271        match args {
272            ArgParser::NoArgs => {
273                cx.emit_err(DocAliasMalformed { span: args.span().unwrap_or(path.span()) });
274            }
275            ArgParser::List(list) => {
276                for i in list.mixed() {
277                    let Some(alias) = i.lit().and_then(|i| i.value_str()) else {
278                        cx.expected_string_literal(i.span(), i.lit());
279                        continue;
280                    };
281
282                    self.add_alias(cx, alias, i.span());
283                }
284            }
285            ArgParser::NameValue(nv) => {
286                let Some(alias) = nv.value_as_str() else {
287                    cx.expected_string_literal(nv.value_span, Some(nv.value_as_lit()));
288                    return;
289                };
290                self.add_alias(cx, alias, nv.value_span);
291            }
292        }
293    }
294
295    fn parse_inline<S: Stage>(
296        &mut self,
297        cx: &mut AcceptContext<'_, '_, S>,
298        path: &OwnedPathParser,
299        args: &ArgParser,
300        inline: DocInline,
301    ) {
302        if let Err(span) = args.no_args() {
303            expected_no_args(cx, span);
304            return;
305        }
306
307        self.attribute.inline.push((inline, path.span()));
308    }
309
310    fn parse_cfg<S: Stage>(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) {
311        // This function replaces cases like `cfg(all())` with `true`.
312        fn simplify_cfg(cfg_entry: &mut CfgEntry) {
313            match cfg_entry {
314                CfgEntry::All(cfgs, span) if cfgs.is_empty() => {
315                    *cfg_entry = CfgEntry::Bool(true, *span)
316                }
317                CfgEntry::Any(cfgs, span) if cfgs.is_empty() => {
318                    *cfg_entry = CfgEntry::Bool(false, *span)
319                }
320                CfgEntry::Not(cfg, _) => simplify_cfg(cfg),
321                _ => {}
322            }
323        }
324        if let Some(mut cfg_entry) = super::cfg::parse_cfg(cx, args) {
325            simplify_cfg(&mut cfg_entry);
326            self.attribute.cfg.push(cfg_entry);
327        }
328    }
329
330    fn parse_auto_cfg<S: Stage>(
331        &mut self,
332        cx: &mut AcceptContext<'_, '_, S>,
333        path: &OwnedPathParser,
334        args: &ArgParser,
335    ) {
336        match args {
337            ArgParser::NoArgs => {
338                self.attribute.auto_cfg_change.push((true, path.span()));
339            }
340            ArgParser::List(list) => {
341                for meta in list.mixed() {
342                    let MetaItemOrLitParser::MetaItemParser(item) = meta else {
343                        cx.emit_lint(
344                            rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
345                            AttributeLintKind::DocAutoCfgExpectsHideOrShow,
346                            meta.span(),
347                        );
348                        continue;
349                    };
350                    let (kind, attr_name) = match item.path().word_sym() {
351                        Some(sym::hide) => (HideOrShow::Hide, sym::hide),
352                        Some(sym::show) => (HideOrShow::Show, sym::show),
353                        _ => {
354                            cx.emit_lint(
355                                rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
356                                AttributeLintKind::DocAutoCfgExpectsHideOrShow,
357                                item.span(),
358                            );
359                            continue;
360                        }
361                    };
362                    let ArgParser::List(list) = item.args() else {
363                        cx.emit_lint(
364                            rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
365                            AttributeLintKind::DocAutoCfgHideShowExpectsList { attr_name },
366                            item.span(),
367                        );
368                        continue;
369                    };
370
371                    let mut cfg_hide_show = CfgHideShow { kind, values: ThinVec::new() };
372
373                    for item in list.mixed() {
374                        let MetaItemOrLitParser::MetaItemParser(sub_item) = item else {
375                            cx.emit_lint(
376                                rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
377                                AttributeLintKind::DocAutoCfgHideShowUnexpectedItem { attr_name },
378                                item.span(),
379                            );
380                            continue;
381                        };
382                        match sub_item.args() {
383                            a @ (ArgParser::NoArgs | ArgParser::NameValue(_)) => {
384                                let Some(name) = sub_item.path().word_sym() else {
385                                    // FIXME: remove this method once merged and uncomment the line
386                                    // below instead.
387                                    // cx.expected_identifier(sub_item.path().span());
388                                    cx.emit_lint(
389                                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
390                                        AttributeLintKind::MalformedDoc,
391                                        sub_item.path().span(),
392                                    );
393                                    continue;
394                                };
395                                if let Ok(CfgEntry::NameValue { name, value, .. }) =
396                                    super::cfg::parse_name_value(
397                                        name,
398                                        sub_item.path().span(),
399                                        a.name_value(),
400                                        sub_item.span(),
401                                        cx,
402                                    )
403                                {
404                                    cfg_hide_show.values.push(CfgInfo {
405                                        name,
406                                        name_span: sub_item.path().span(),
407                                        // If `value` is `Some`, `a.name_value()` will always return
408                                        // `Some` as well.
409                                        value: value
410                                            .map(|v| (v, a.name_value().unwrap().value_span)),
411                                    })
412                                }
413                            }
414                            _ => {
415                                cx.emit_lint(
416                                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
417                                    AttributeLintKind::DocAutoCfgHideShowUnexpectedItem {
418                                        attr_name,
419                                    },
420                                    sub_item.span(),
421                                );
422                                continue;
423                            }
424                        }
425                    }
426                    self.attribute.auto_cfg.push((cfg_hide_show, path.span()));
427                }
428            }
429            ArgParser::NameValue(nv) => {
430                let MetaItemLit { kind: LitKind::Bool(bool_value), span, .. } = nv.value_as_lit()
431                else {
432                    cx.emit_lint(
433                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
434                        AttributeLintKind::DocAutoCfgWrongLiteral,
435                        nv.value_span,
436                    );
437                    return;
438                };
439                self.attribute.auto_cfg_change.push((*bool_value, *span));
440            }
441        }
442    }
443
444    fn parse_single_doc_attr_item<S: Stage>(
445        &mut self,
446        cx: &mut AcceptContext<'_, '_, S>,
447        mip: &MetaItemParser,
448    ) {
449        let path = mip.path();
450        let args = mip.args();
451
452        macro_rules! no_args {
453            ($ident: ident) => {{
454                if let Err(span) = args.no_args() {
455                    expected_no_args(cx, span);
456                    return;
457                }
458
459                // FIXME: It's errorring when the attribute is passed multiple times on the command
460                // line.
461                // The right fix for this would be to only check this rule if the attribute is
462                // not set on the command line but directly in the code.
463                // if self.attribute.$ident.is_some() {
464                //     cx.duplicate_key(path.span(), path.word_sym().unwrap());
465                //     return;
466                // }
467
468                self.attribute.$ident = Some(path.span());
469            }};
470        }
471        macro_rules! no_args_and_not_crate_level {
472            ($ident: ident) => {{
473                if let Err(span) = args.no_args() {
474                    expected_no_args(cx, span);
475                    return;
476                }
477                let span = path.span();
478                if !check_attr_not_crate_level(cx, span, sym::$ident) {
479                    return;
480                }
481                self.attribute.$ident = Some(span);
482            }};
483        }
484        macro_rules! no_args_and_crate_level {
485            ($ident: ident) => {{
486                no_args_and_crate_level!($ident, |span| {});
487            }};
488            ($ident: ident, |$span:ident| $extra_validation:block) => {{
489                if let Err(span) = args.no_args() {
490                    expected_no_args(cx, span);
491                    return;
492                }
493                let $span = path.span();
494                if !check_attr_crate_level(cx, $span) {
495                    return;
496                }
497                $extra_validation
498                self.attribute.$ident = Some($span);
499            }};
500        }
501        macro_rules! string_arg_and_crate_level {
502            ($ident: ident) => {{
503                let Some(nv) = args.name_value() else {
504                    expected_name_value(cx, args.span().unwrap_or(path.span()), path.word_sym());
505                    return;
506                };
507
508                let Some(s) = nv.value_as_str() else {
509                    expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
510                    return;
511                };
512
513                if !check_attr_crate_level(cx, path.span()) {
514                    return;
515                }
516
517                // FIXME: It's errorring when the attribute is passed multiple times on the command
518                // line.
519                // The right fix for this would be to only check this rule if the attribute is
520                // not set on the command line but directly in the code.
521                // if self.attribute.$ident.is_some() {
522                //     cx.duplicate_key(path.span(), path.word_sym().unwrap());
523                //     return;
524                // }
525
526                self.attribute.$ident = Some((s, path.span()));
527            }};
528        }
529
530        match path.word_sym() {
531            Some(sym::alias) => self.parse_alias(cx, path, args),
532            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),
533            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),
534            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),
535            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),
536            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),
537            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),
538            Some(sym::issue_tracker_base_url) => {
539                {
    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)
540            }
541            Some(sym::inline) => self.parse_inline(cx, path, args, DocInline::Inline),
542            Some(sym::no_inline) => self.parse_inline(cx, path, args, DocInline::NoInline),
543            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),
544            Some(sym::cfg) => self.parse_cfg(cx, args),
545            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),
546            Some(sym::keyword) => parse_keyword_and_attribute(
547                cx,
548                path,
549                args,
550                &mut self.attribute.keyword,
551                sym::keyword,
552            ),
553            Some(sym::attribute) => parse_keyword_and_attribute(
554                cx,
555                path,
556                args,
557                &mut self.attribute.attribute,
558                sym::attribute,
559            ),
560            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),
561            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),
562            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| {
563                if !cx.features().rustdoc_internals() {
564                    feature_err(
565                        cx.sess(),
566                        sym::rustdoc_internals,
567                        span,
568                        msg!("the `#[doc(rust_logo)]` attribute is used for Rust branding"),
569                    )
570                    .emit();
571                }
572            }),
573            Some(sym::auto_cfg) => self.parse_auto_cfg(cx, path, args),
574            Some(sym::test) => {
575                let Some(list) = args.list() else {
576                    cx.emit_lint(
577                        rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
578                        AttributeLintKind::DocTestTakesList,
579                        args.span().unwrap_or(path.span()),
580                    );
581                    return;
582                };
583
584                for i in list.mixed() {
585                    match i {
586                        MetaItemOrLitParser::MetaItemParser(mip) => {
587                            self.parse_single_test_doc_attr_item(cx, mip);
588                        }
589                        MetaItemOrLitParser::Lit(lit) => {
590                            // FIXME: remove this method once merged and uncomment the line
591                            // below instead.
592                            // cx.unexpected_literal(lit.span);
593                            cx.emit_lint(
594                                rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
595                                AttributeLintKind::MalformedDoc,
596                                lit.span,
597                            );
598                        }
599                    }
600                }
601            }
602            Some(sym::spotlight) => {
603                cx.emit_lint(
604                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
605                    AttributeLintKind::DocUnknownSpotlight { span: path.span() },
606                    path.span(),
607                );
608            }
609            Some(sym::include) if let Some(nv) = args.name_value() => {
610                let inner = match cx.attr_style {
611                    AttrStyle::Outer => "",
612                    AttrStyle::Inner => "!",
613                };
614                cx.emit_lint(
615                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
616                    AttributeLintKind::DocUnknownInclude {
617                        inner,
618                        value: nv.value_as_lit().symbol,
619                        span: path.span(),
620                    },
621                    path.span(),
622                );
623            }
624            Some(name @ (sym::passes | sym::no_default_passes)) => {
625                cx.emit_lint(
626                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
627                    AttributeLintKind::DocUnknownPasses { name, span: path.span() },
628                    path.span(),
629                );
630            }
631            Some(sym::plugins) => {
632                cx.emit_lint(
633                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
634                    AttributeLintKind::DocUnknownPlugins { span: path.span() },
635                    path.span(),
636                );
637            }
638            Some(name) => {
639                cx.emit_lint(
640                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
641                    AttributeLintKind::DocUnknownAny { name },
642                    path.span(),
643                );
644            }
645            None => {
646                let full_name =
647                    path.segments().map(|s| s.as_str()).intersperse("::").collect::<String>();
648                cx.emit_lint(
649                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
650                    AttributeLintKind::DocUnknownAny { name: Symbol::intern(&full_name) },
651                    path.span(),
652                );
653            }
654        }
655    }
656
657    fn accept_single_doc_attr<S: Stage>(
658        &mut self,
659        cx: &mut AcceptContext<'_, '_, S>,
660        args: &ArgParser,
661    ) {
662        match args {
663            ArgParser::NoArgs => {
664                let suggestions = cx.suggestions();
665                let span = cx.attr_span;
666                cx.emit_lint(
667                    rustc_session::lint::builtin::INVALID_DOC_ATTRIBUTES,
668                    AttributeLintKind::IllFormedAttributeInput { suggestions, docs: None },
669                    span,
670                );
671            }
672            ArgParser::List(items) => {
673                for i in items.mixed() {
674                    match i {
675                        MetaItemOrLitParser::MetaItemParser(mip) => {
676                            self.nb_doc_attrs += 1;
677                            self.parse_single_doc_attr_item(cx, mip);
678                        }
679                        MetaItemOrLitParser::Lit(lit) => {
680                            expected_name_value(cx, lit.span, None);
681                        }
682                    }
683                }
684            }
685            ArgParser::NameValue(nv) => {
686                if nv.value_as_str().is_none() {
687                    expected_string_literal(cx, nv.value_span, Some(nv.value_as_lit()));
688                } else {
689                    {
    ::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!(
690                        "Should have been handled at the same time as sugar-syntaxed doc comments"
691                    );
692                }
693            }
694        }
695    }
696}
697
698impl<S: Stage> AttributeParser<S> for DocParser {
699    const ATTRIBUTES: AcceptMapping<Self, S> = &[(
700        &[sym::doc],
701        ::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!(
702            List: &[
703                "alias",
704                "attribute",
705                "hidden",
706                "html_favicon_url",
707                "html_logo_url",
708                "html_no_source",
709                "html_playground_url",
710                "html_root_url",
711                "issue_tracker_base_url",
712                "inline",
713                "no_inline",
714                "masked",
715                "cfg",
716                "notable_trait",
717                "keyword",
718                "fake_variadic",
719                "search_unbox",
720                "rust_logo",
721                "auto_cfg",
722                "test",
723                "spotlight",
724                "include",
725                "no_default_passes",
726                "passes",
727                "plugins",
728            ],
729            NameValueStr: "string"
730        ),
731        |this, cx, args| {
732            this.accept_single_doc_attr(cx, args);
733        },
734    )];
735    // FIXME: Currently emitted from 2 different places, generating duplicated warnings.
736    const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
737    // const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowListWarnRest(&[
738    //     Allow(Target::ExternCrate),
739    //     Allow(Target::Use),
740    //     Allow(Target::Static),
741    //     Allow(Target::Const),
742    //     Allow(Target::Fn),
743    //     Allow(Target::Mod),
744    //     Allow(Target::ForeignMod),
745    //     Allow(Target::TyAlias),
746    //     Allow(Target::Enum),
747    //     Allow(Target::Variant),
748    //     Allow(Target::Struct),
749    //     Allow(Target::Field),
750    //     Allow(Target::Union),
751    //     Allow(Target::Trait),
752    //     Allow(Target::TraitAlias),
753    //     Allow(Target::Impl { of_trait: true }),
754    //     Allow(Target::Impl { of_trait: false }),
755    //     Allow(Target::AssocConst),
756    //     Allow(Target::Method(MethodKind::Inherent)),
757    //     Allow(Target::Method(MethodKind::Trait { body: true })),
758    //     Allow(Target::Method(MethodKind::Trait { body: false })),
759    //     Allow(Target::Method(MethodKind::TraitImpl)),
760    //     Allow(Target::AssocTy),
761    //     Allow(Target::ForeignFn),
762    //     Allow(Target::ForeignStatic),
763    //     Allow(Target::ForeignTy),
764    //     Allow(Target::MacroDef),
765    //     Allow(Target::Crate),
766    //     Error(Target::WherePredicate),
767    // ]);
768
769    fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option<AttributeKind> {
770        if self.nb_doc_attrs != 0 {
771            Some(AttributeKind::Doc(Box::new(self.attribute)))
772        } else {
773            None
774        }
775    }
776}