@@ -8,7 +8,7 @@ use rustc_middle::hir::map::Map;
8
8
use rustc_middle:: ty:: query:: Providers ;
9
9
use rustc_middle:: ty:: TyCtxt ;
10
10
11
- use rustc_ast:: { Attribute , Lit , LitKind , NestedMetaItem } ;
11
+ use rustc_ast:: { AttrStyle , Attribute , Lit , LitKind , NestedMetaItem } ;
12
12
use rustc_errors:: { pluralize, struct_span_err, Applicability } ;
13
13
use rustc_hir as hir;
14
14
use rustc_hir:: def_id:: LocalDefId ;
@@ -22,7 +22,7 @@ use rustc_session::lint::builtin::{
22
22
} ;
23
23
use rustc_session:: parse:: feature_err;
24
24
use rustc_span:: symbol:: { sym, Symbol } ;
25
- use rustc_span:: { Span , DUMMY_SP } ;
25
+ use rustc_span:: { MultiSpan , Span , DUMMY_SP } ;
26
26
27
27
pub ( crate ) fn target_from_impl_item < ' tcx > (
28
28
tcx : TyCtxt < ' tcx > ,
@@ -67,6 +67,7 @@ impl CheckAttrVisitor<'tcx> {
67
67
item : Option < ItemLike < ' _ > > ,
68
68
) {
69
69
let mut is_valid = true ;
70
+ let mut specified_inline = None ;
70
71
let attrs = self . tcx . hir ( ) . attrs ( hir_id) ;
71
72
for attr in attrs {
72
73
is_valid &= match attr. name_or_empty ( ) {
@@ -77,7 +78,7 @@ impl CheckAttrVisitor<'tcx> {
77
78
sym:: track_caller => {
78
79
self . check_track_caller ( hir_id, & attr. span , attrs, span, target)
79
80
}
80
- sym:: doc => self . check_doc_attrs ( attr, hir_id, target) ,
81
+ sym:: doc => self . check_doc_attrs ( attr, hir_id, target, & mut specified_inline ) ,
81
82
sym:: no_link => self . check_no_link ( hir_id, & attr, span, target) ,
82
83
sym:: export_name => self . check_export_name ( hir_id, & attr, span, target) ,
83
84
sym:: rustc_args_required_const => {
@@ -564,7 +565,71 @@ impl CheckAttrVisitor<'tcx> {
564
565
true
565
566
}
566
567
567
- fn check_attr_crate_level (
568
+ /// Checks `#[doc(inline)]`/`#[doc(no_inline)]` attributes. Returns `true` if valid.
569
+ ///
570
+ /// A doc inlining attribute is invalid if it is applied to a non-`use` item, or
571
+ /// if there are conflicting attributes for one item.
572
+ ///
573
+ /// `specified_inline` is used to keep track of whether we have
574
+ /// already seen an inlining attribute for this item.
575
+ /// If so, `specified_inline` holds the value and the span of
576
+ /// the first `inline`/`no_inline` attribute.
577
+ fn check_doc_inline (
578
+ & self ,
579
+ attr : & Attribute ,
580
+ meta : & NestedMetaItem ,
581
+ hir_id : HirId ,
582
+ target : Target ,
583
+ specified_inline : & mut Option < ( bool , Span ) > ,
584
+ ) -> bool {
585
+ if target == Target :: Use {
586
+ let do_inline = meta. name_or_empty ( ) == sym:: inline;
587
+ if let Some ( ( prev_inline, prev_span) ) = * specified_inline {
588
+ if do_inline != prev_inline {
589
+ let mut spans = MultiSpan :: from_spans ( vec ! [ prev_span, meta. span( ) ] ) ;
590
+ spans. push_span_label ( prev_span, String :: from ( "this attribute..." ) ) ;
591
+ spans. push_span_label (
592
+ meta. span ( ) ,
593
+ String :: from ( "...conflicts with this attribute" ) ,
594
+ ) ;
595
+ self . tcx
596
+ . sess
597
+ . struct_span_err ( spans, "conflicting doc inlining attributes" )
598
+ . help ( "remove one of the conflicting attributes" )
599
+ . emit ( ) ;
600
+ return false ;
601
+ }
602
+ true
603
+ } else {
604
+ * specified_inline = Some ( ( do_inline, meta. span ( ) ) ) ;
605
+ true
606
+ }
607
+ } else {
608
+ self . tcx . struct_span_lint_hir (
609
+ INVALID_DOC_ATTRIBUTES ,
610
+ hir_id,
611
+ meta. span ( ) ,
612
+ |lint| {
613
+ let mut err = lint. build (
614
+ "this attribute can only be applied to a `use` item" ,
615
+ ) ;
616
+ err. span_label ( meta. span ( ) , "only applicable on `use` items" ) ;
617
+ if attr. style == AttrStyle :: Outer {
618
+ err. span_label (
619
+ self . tcx . hir ( ) . span ( hir_id) ,
620
+ "not a `use` item" ,
621
+ ) ;
622
+ }
623
+ err. note ( "read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#docno_inlinedocinline for more information" )
624
+ . emit ( ) ;
625
+ } ,
626
+ ) ;
627
+ false
628
+ }
629
+ }
630
+
631
+ /// Checks that an attribute is *not* used at the crate level. Returns `true` if valid.
632
+ fn check_attr_not_crate_level (
568
633
& self ,
569
634
meta : & NestedMetaItem ,
570
635
hir_id : HirId ,
@@ -586,40 +651,103 @@ impl CheckAttrVisitor<'tcx> {
586
651
true
587
652
}
588
653
589
- fn check_doc_attrs ( & self , attr : & Attribute , hir_id : HirId , target : Target ) -> bool {
654
+ /// Checks that an attribute is used at the crate level. Returns `true` if valid.
655
+ fn check_attr_crate_level (
656
+ & self ,
657
+ attr : & Attribute ,
658
+ meta : & NestedMetaItem ,
659
+ hir_id : HirId ,
660
+ ) -> bool {
661
+ if hir_id != CRATE_HIR_ID {
662
+ self . tcx . struct_span_lint_hir (
663
+ INVALID_DOC_ATTRIBUTES ,
664
+ hir_id,
665
+ meta. span ( ) ,
666
+ |lint| {
667
+ let mut err = lint. build (
668
+ "this attribute can only be applied at the crate level" ,
669
+ ) ;
670
+ if attr. style == AttrStyle :: Outer && self . tcx . hir ( ) . get_parent_item ( hir_id) == CRATE_HIR_ID {
671
+ if let Ok ( mut src) =
672
+ self . tcx . sess . source_map ( ) . span_to_snippet ( attr. span )
673
+ {
674
+ src. insert ( 1 , '!' ) ;
675
+ err. span_suggestion_verbose (
676
+ attr. span ,
677
+ "to apply to the crate, use an inner attribute" ,
678
+ src,
679
+ Applicability :: MaybeIncorrect ,
680
+ ) ;
681
+ } else {
682
+ err. span_help (
683
+ attr. span ,
684
+ "to apply to the crate, use an inner attribute" ,
685
+ ) ;
686
+ }
687
+ }
688
+ err. note ( "read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information" )
689
+ . emit ( ) ;
690
+ } ,
691
+ ) ;
692
+ return false ;
693
+ }
694
+ true
695
+ }
696
+
697
+ /// Runs various checks on `#[doc]` attributes. Returns `true` if valid.
698
+ ///
699
+ /// `specified_inline` should be initialized to `None` and kept for the scope
700
+ /// of one item. Read the documentation of [`check_doc_inline`] for more information.
701
+ ///
702
+ /// [`check_doc_inline`]: Self::check_doc_inline
703
+ fn check_doc_attrs (
704
+ & self ,
705
+ attr : & Attribute ,
706
+ hir_id : HirId ,
707
+ target : Target ,
708
+ specified_inline : & mut Option < ( bool , Span ) > ,
709
+ ) -> bool {
590
710
let mut is_valid = true ;
591
711
592
712
if let Some ( list) = attr. meta ( ) . and_then ( |mi| mi. meta_item_list ( ) . map ( |l| l. to_vec ( ) ) ) {
593
713
for meta in list {
594
714
if let Some ( i_meta) = meta. meta_item ( ) {
595
715
match i_meta. name_or_empty ( ) {
596
716
sym:: alias
597
- if !self . check_attr_crate_level ( & meta, hir_id, "alias" )
717
+ if !self . check_attr_not_crate_level ( & meta, hir_id, "alias" )
598
718
|| !self . check_doc_alias ( & meta, hir_id, target) =>
599
719
{
600
720
is_valid = false
601
721
}
602
722
603
723
sym:: keyword
604
- if !self . check_attr_crate_level ( & meta, hir_id, "keyword" )
724
+ if !self . check_attr_not_crate_level ( & meta, hir_id, "keyword" )
605
725
|| !self . check_doc_keyword ( & meta, hir_id) =>
606
726
{
607
727
is_valid = false
608
728
}
609
729
610
- sym:: test if CRATE_HIR_ID != hir_id => {
611
- self . tcx . struct_span_lint_hir (
612
- INVALID_DOC_ATTRIBUTES ,
730
+ sym:: html_favicon_url
731
+ | sym:: html_logo_url
732
+ | sym:: html_playground_url
733
+ | sym:: issue_tracker_base_url
734
+ | sym:: html_root_url
735
+ | sym:: html_no_source
736
+ | sym:: test
737
+ if !self . check_attr_crate_level ( & attr, & meta, hir_id) =>
738
+ {
739
+ is_valid = false ;
740
+ }
741
+
742
+ sym:: inline | sym:: no_inline
743
+ if !self . check_doc_inline (
744
+ & attr,
745
+ & meta,
613
746
hir_id,
614
- meta. span ( ) ,
615
- |lint| {
616
- lint. build (
617
- "`#![doc(test(...)]` is only allowed \
618
- as a crate-level attribute",
619
- )
620
- . emit ( ) ;
621
- } ,
622
- ) ;
747
+ target,
748
+ specified_inline,
749
+ ) =>
750
+ {
623
751
is_valid = false ;
624
752
}
625
753
0 commit comments