@@ -844,6 +844,21 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs: &[
844844
845845 let mut cb = fake_broken_link_callback;
846846
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+
847862 // disable smart punctuation to pick up ['link'] more easily
848863 let opts = main_body_opts ( ) - Options :: ENABLE_SMART_PUNCTUATION ;
849864 let parser = pulldown_cmark:: Parser :: new_with_broken_link_callback ( & doc, opts, Some ( & mut cb) ) ;
@@ -867,12 +882,64 @@ enum Container {
867882 List ( usize ) ,
868883}
869884
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+ }
876943}
877944
878945/// Checks parsed documentation.
@@ -906,40 +973,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
906973
907974 let mut containers = Vec :: new ( ) ;
908975
909- let mut code_cluster = None ;
910-
911976 let mut events = events. peekable ( ) ;
912977
913978 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- } ;
943979 match event {
944980 Html ( tag) | InlineHtml ( tag) => {
945981 if tag. starts_with ( "<code" ) {
0 commit comments