Skip to main content

rustc_attr_parsing/attributes/
doc.rs

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