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