Skip to main content

rustc_attr_parsing/attributes/
doc.rs

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