1- use clippy_utils:: diagnostics:: { span_lint, span_lint_and_note} ;
1+ use clippy_utils:: diagnostics:: { span_lint, span_lint_and_help, span_lint_and_note} ;
2+ use clippy_utils:: source:: first_line_of_span;
23use clippy_utils:: ty:: { implements_trait, is_type_diagnostic_item} ;
34use clippy_utils:: { is_entrypoint_fn, is_expn_of, match_panic_def_id, method_chain_args, return_ty} ;
45use if_chain:: if_chain;
@@ -37,7 +38,8 @@ declare_clippy_lint! {
3738 /// consider that.
3839 ///
3940 /// **Known problems:** Lots of bad docs won’t be fixed, what the lint checks
40- /// for is limited, and there are still false positives.
41+ /// for is limited, and there are still false positives. HTML elements and their
42+ /// content are not linted.
4143 ///
4244 /// In addition, when writing documentation comments, including `[]` brackets
4345 /// inside a link text would trip the parser. Therfore, documenting link with
@@ -469,11 +471,11 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
469471 spans : & [ ( usize , Span ) ] ,
470472) -> DocHeaders {
471473 // true if a safety header was found
472- use pulldown_cmark:: CodeBlockKind ;
473474 use pulldown_cmark:: Event :: {
474475 Code , End , FootnoteReference , HardBreak , Html , Rule , SoftBreak , Start , TaskListMarker , Text ,
475476 } ;
476- use pulldown_cmark:: Tag :: { CodeBlock , Heading , Link } ;
477+ use pulldown_cmark:: Tag :: { CodeBlock , Heading , Item , Link , Paragraph } ;
478+ use pulldown_cmark:: { CodeBlockKind , CowStr } ;
477479
478480 let mut headers = DocHeaders {
479481 safety : false ,
@@ -485,6 +487,9 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
485487 let mut in_heading = false ;
486488 let mut is_rust = false ;
487489 let mut edition = None ;
490+ let mut ticks_unbalanced = false ;
491+ let mut text_to_check: Vec < ( CowStr < ' _ > , Span ) > = Vec :: new ( ) ;
492+ let mut paragraph_span = spans. get ( 0 ) . expect ( "function isn't called if doc comment is empty" ) . 1 ;
488493 for ( event, range) in events {
489494 match event {
490495 Start ( CodeBlock ( ref kind) ) => {
@@ -510,13 +515,42 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
510515 } ,
511516 Start ( Link ( _, url, _) ) => in_link = Some ( url) ,
512517 End ( Link ( ..) ) => in_link = None ,
513- Start ( Heading ( _) ) => in_heading = true ,
514- End ( Heading ( _) ) => in_heading = false ,
518+ Start ( Heading ( _) | Paragraph | Item ) => {
519+ if let Start ( Heading ( _) ) = event {
520+ in_heading = true ;
521+ }
522+ ticks_unbalanced = false ;
523+ let ( _, span) = get_current_span ( spans, range. start ) ;
524+ paragraph_span = first_line_of_span ( cx, span) ;
525+ } ,
526+ End ( Heading ( _) | Paragraph | Item ) => {
527+ if let End ( Heading ( _) ) = event {
528+ in_heading = false ;
529+ }
530+ if ticks_unbalanced {
531+ span_lint_and_help (
532+ cx,
533+ DOC_MARKDOWN ,
534+ paragraph_span,
535+ "backticks are unbalanced" ,
536+ None ,
537+ "a backtick may be missing a pair" ,
538+ ) ;
539+ } else {
540+ for ( text, span) in text_to_check {
541+ check_text ( cx, valid_idents, & text, span) ;
542+ }
543+ }
544+ text_to_check = Vec :: new ( ) ;
545+ } ,
515546 Start ( _tag) | End ( _tag) => ( ) , // We don't care about other tags
516547 Html ( _html) => ( ) , // HTML is weird, just ignore it
517548 SoftBreak | HardBreak | TaskListMarker ( _) | Code ( _) | Rule => ( ) ,
518549 FootnoteReference ( text) | Text ( text) => {
519- if Some ( & text) == in_link. as_ref ( ) {
550+ let ( begin, span) = get_current_span ( spans, range. start ) ;
551+ paragraph_span = paragraph_span. with_hi ( span. hi ( ) ) ;
552+ ticks_unbalanced |= text. contains ( '`' ) ;
553+ if Some ( & text) == in_link. as_ref ( ) || ticks_unbalanced {
520554 // Probably a link of the form `<http://example.com>`
521555 // Which are represented as a link to "http://example.com" with
522556 // text "http://example.com" by pulldown-cmark
@@ -525,11 +559,6 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
525559 headers. safety |= in_heading && text. trim ( ) == "Safety" ;
526560 headers. errors |= in_heading && text. trim ( ) == "Errors" ;
527561 headers. panics |= in_heading && text. trim ( ) == "Panics" ;
528- let index = match spans. binary_search_by ( |c| c. 0 . cmp ( & range. start ) ) {
529- Ok ( o) => o,
530- Err ( e) => e - 1 ,
531- } ;
532- let ( begin, span) = spans[ index] ;
533562 if in_code {
534563 if is_rust {
535564 let edition = edition. unwrap_or_else ( || cx. tcx . sess . edition ( ) ) ;
@@ -538,15 +567,22 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
538567 } else {
539568 // Adjust for the beginning of the current `Event`
540569 let span = span. with_lo ( span. lo ( ) + BytePos :: from_usize ( range. start - begin) ) ;
541-
542- check_text ( cx, valid_idents, & text, span) ;
570+ text_to_check. push ( ( text, span) ) ;
543571 }
544572 } ,
545573 }
546574 }
547575 headers
548576}
549577
578+ fn get_current_span ( spans : & [ ( usize , Span ) ] , idx : usize ) -> ( usize , Span ) {
579+ let index = match spans. binary_search_by ( |c| c. 0 . cmp ( & idx) ) {
580+ Ok ( o) => o,
581+ Err ( e) => e - 1 ,
582+ } ;
583+ spans[ index]
584+ }
585+
550586fn check_code ( cx : & LateContext < ' _ > , text : & str , edition : Edition , span : Span ) {
551587 fn has_needless_main ( code : & str , edition : Edition ) -> bool {
552588 rustc_driver:: catch_fatal_errors ( || {
0 commit comments