@@ -844,6 +844,21 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
844
844
845
845
let mut cb = fake_broken_link_callback;
846
846
847
+ check_for_code_clusters (
848
+ cx,
849
+ pulldown_cmark:: Parser :: new_with_broken_link_callback (
850
+ & doc,
851
+ main_body_opts ( ) - Options :: ENABLE_SMART_PUNCTUATION ,
852
+ Some ( & mut cb) ,
853
+ )
854
+ . into_offset_iter ( ) ,
855
+ & doc,
856
+ Fragments {
857
+ doc : & doc,
858
+ fragments : & fragments,
859
+ } ,
860
+ ) ;
861
+
847
862
// disable smart punctuation to pick up ['link'] more easily
848
863
let opts = main_body_opts ( ) - Options :: ENABLE_SMART_PUNCTUATION ;
849
864
let parser = pulldown_cmark:: Parser :: new_with_broken_link_callback ( & doc, opts, Some ( & mut cb) ) ;
@@ -867,12 +882,64 @@ enum Container {
867
882
List ( usize ) ,
868
883
}
869
884
870
- #[ derive( Clone , Copy , Eq , PartialEq ) ]
871
- enum CodeCluster {
872
- // true means already in a link, so only needs to be followed by code
873
- // false means we've hit code, and need to find a link
874
- First ( usize , bool ) ,
875
- Nth ( usize , usize ) ,
885
+ /// Scan the documentation for code links that are back-to-back with code spans.
886
+ ///
887
+ /// This is done separately from the rest of the docs, because that makes it easier to produce
888
+ /// the correct messages.
889
+ fn check_for_code_clusters < ' a , Events : Iterator < Item = ( pulldown_cmark:: Event < ' a > , Range < usize > ) > > (
890
+ cx : & LateContext < ' _ > ,
891
+ events : Events ,
892
+ doc : & str ,
893
+ fragments : Fragments < ' _ > ,
894
+ ) {
895
+ let mut events = events. peekable ( ) ;
896
+ let mut code_starts_at = None ;
897
+ let mut code_ends_at = None ;
898
+ let mut code_includes_link = false ;
899
+ while let Some ( ( event, range) ) = events. next ( ) {
900
+ match event {
901
+ Start ( Link { .. } ) if matches ! ( events. peek( ) , Some ( ( Code ( _) , _range) ) ) => {
902
+ if code_starts_at. is_some ( ) {
903
+ code_ends_at = Some ( range. end ) ;
904
+ } else {
905
+ code_starts_at = Some ( range. start ) ;
906
+ }
907
+ code_includes_link = true ;
908
+ // skip the nested "code", because we're already handling it here
909
+ let _ = events. next ( ) ;
910
+ } ,
911
+ Code ( _) => {
912
+ if code_starts_at. is_some ( ) {
913
+ code_ends_at = Some ( range. end ) ;
914
+ } else {
915
+ code_starts_at = Some ( range. start ) ;
916
+ }
917
+ } ,
918
+ End ( TagEnd :: Link ) => { } ,
919
+ _ => {
920
+ if let Some ( start) = code_starts_at
921
+ && let Some ( end) = code_ends_at
922
+ && code_includes_link
923
+ {
924
+ if let Some ( span) = fragments. span ( cx, start..end) {
925
+ span_lint_and_then ( cx, DOC_LINK_CODE , span, "code link adjacent to code text" , |diag| {
926
+ let sugg = format ! ( "<code>{}</code>" , doc[ start..end] . replace( '`' , "" ) ) ;
927
+ diag. span_suggestion_verbose (
928
+ span,
929
+ "wrap the entire group in `<code>` tags" ,
930
+ sugg,
931
+ Applicability :: MaybeIncorrect ,
932
+ ) ;
933
+ diag. help ( "separate code snippets will be shown with a gap" ) ;
934
+ } ) ;
935
+ }
936
+ }
937
+ code_includes_link = false ;
938
+ code_starts_at = None ;
939
+ code_ends_at = None ;
940
+ } ,
941
+ }
942
+ }
876
943
}
877
944
878
945
/// Checks parsed documentation.
@@ -906,40 +973,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
906
973
907
974
let mut containers = Vec :: new ( ) ;
908
975
909
- let mut code_cluster = None ;
910
-
911
976
let mut events = events. peekable ( ) ;
912
977
913
978
while let Some ( ( event, range) ) = events. next ( ) {
914
- code_cluster = match ( code_cluster, & event) {
915
- ( None , Code ( _) ) if in_link. is_some ( ) && doc. as_bytes ( ) . get ( range. start . wrapping_sub ( 1 ) ) == Some ( & b'[' ) => {
916
- Some ( CodeCluster :: First ( range. start - 1 , true ) )
917
- } ,
918
- ( None , Code ( _) ) => Some ( CodeCluster :: First ( range. start , false ) ) ,
919
- ( Some ( CodeCluster :: First ( pos, _) ) , Start ( Link { .. } ) ) | ( Some ( CodeCluster :: First ( pos, true ) ) , Code ( _) ) => {
920
- Some ( CodeCluster :: Nth ( pos, range. end ) )
921
- } ,
922
- ( Some ( CodeCluster :: Nth ( start, end) ) , Code ( _) | Start ( Link { .. } ) ) => {
923
- Some ( CodeCluster :: Nth ( start, range. end . max ( end) ) )
924
- } ,
925
- ( code_cluster @ Some ( _) , Code ( _) | End ( TagEnd :: Link ) ) => code_cluster,
926
- ( Some ( CodeCluster :: First ( _, _) ) | None , _) => None ,
927
- ( Some ( CodeCluster :: Nth ( start, end) ) , _) => {
928
- if let Some ( span) = fragments. span ( cx, start..end) {
929
- span_lint_and_then ( cx, DOC_LINK_CODE , span, "code link adjacent to code text" , |diag| {
930
- let sugg = format ! ( "<code>{}</code>" , doc[ start..end] . replace( '`' , "" ) ) ;
931
- diag. span_suggestion_verbose (
932
- span,
933
- "wrap the entire group in `<code>` tags" ,
934
- sugg,
935
- Applicability :: MaybeIncorrect ,
936
- ) ;
937
- diag. help ( "separate code snippets will be shown with a gap" ) ;
938
- } ) ;
939
- }
940
- None
941
- } ,
942
- } ;
943
979
match event {
944
980
Html ( tag) | InlineHtml ( tag) => {
945
981
if tag. starts_with ( "<code" ) {
0 commit comments