Skip to main content

rustc_attr_parsing/attributes/
doc.rs

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