@@ -3,10 +3,11 @@ use crate::clean::*;
33use crate :: core:: DocContext ;
44use crate :: fold:: DocFolder ;
55use crate :: html:: markdown:: opts;
6+ use core:: ops:: Range ;
67use pulldown_cmark:: { Event , Parser } ;
7- use rustc_hir:: hir_id:: HirId ;
8+ // use rustc_hir::hir_id::HirId;
89use rustc_session:: lint;
9- use rustc_span:: Span ;
10+ // use rustc_span::Span;
1011
1112pub const CHECK_INVALID_HTML_TAGS : Pass = Pass {
1213 name : "check-invalid-html-tags" ,
@@ -36,62 +37,61 @@ const ALLOWED_UNCLOSED: &[&str] = &[
3637] ;
3738
3839fn drop_tag (
39- cx : & DocContext < ' _ > ,
40- tags : & mut Vec < String > ,
40+ tags : & mut Vec < ( String , Range < usize > ) > ,
4141 tag_name : String ,
42- hir_id : HirId ,
43- sp : Span ,
42+ range : Range < usize > ,
43+ f : & impl Fn ( & str , & Range < usize > ) ,
4444) {
45- if let Some ( pos) = tags. iter ( ) . position ( |t | * t == tag_name) {
45+ if let Some ( pos) = tags. iter ( ) . position ( |( t , _ ) | * t == tag_name) {
4646 for _ in pos + 1 ..tags. len ( ) {
47- if ALLOWED_UNCLOSED . iter ( ) . find ( |& at| at == & tags[ pos + 1 ] ) . is_some ( ) {
47+ if ALLOWED_UNCLOSED . iter ( ) . find ( |& at| at == & tags[ pos + 1 ] . 0 ) . is_some ( ) {
4848 continue ;
4949 }
5050 // `tags` is used as a queue, meaning that everything after `pos` is included inside it.
5151 // So `<h2><h3></h2>` will look like `["h2", "h3"]`. So when closing `h2`, we will still
5252 // have `h3`, meaning the tag wasn't closed as it should have.
53- cx. tcx . struct_span_lint_hir ( lint:: builtin:: INVALID_HTML_TAGS , hir_id, sp, |lint| {
54- lint. build ( & format ! ( "unclosed HTML tag `{}`" , tags[ pos + 1 ] ) ) . emit ( )
55- } ) ;
53+ f ( & format ! ( "unclosed HTML tag `{}`" , tags[ pos + 1 ] . 0 ) , & tags[ pos + 1 ] . 1 ) ;
5654 tags. remove ( pos + 1 ) ;
5755 }
5856 tags. remove ( pos) ;
5957 } else {
6058 // It can happen for example in this case: `<h2></script></h2>` (the `h2` tag isn't required
6159 // but it helps for the visualization).
62- cx. tcx . struct_span_lint_hir ( lint:: builtin:: INVALID_HTML_TAGS , hir_id, sp, |lint| {
63- lint. build ( & format ! ( "unopened HTML tag `{}`" , tag_name) ) . emit ( )
64- } ) ;
60+ f ( & format ! ( "unopened HTML tag `{}`" , tag_name) , & range) ;
6561 }
6662}
6763
68- fn extract_tag ( cx : & DocContext < ' _ > , tags : & mut Vec < String > , text : & str , hir_id : HirId , sp : Span ) {
69- let mut iter = text. chars ( ) . peekable ( ) ;
64+ fn extract_tag (
65+ tags : & mut Vec < ( String , Range < usize > ) > ,
66+ text : & str ,
67+ range : Range < usize > ,
68+ f : & impl Fn ( & str , & Range < usize > ) ,
69+ ) {
70+ let mut iter = text. chars ( ) . enumerate ( ) . peekable ( ) ;
7071
71- while let Some ( c ) = iter. next ( ) {
72+ while let Some ( ( start_pos , c ) ) = iter. next ( ) {
7273 if c == '<' {
7374 let mut tag_name = String :: new ( ) ;
7475 let mut is_closing = false ;
75- while let Some ( & c ) = iter. peek ( ) {
76- // </tag>
77- if c == '/' && tag_name. is_empty ( ) {
76+ while let Some ( ( pos , c ) ) = iter. peek ( ) {
77+ // Checking if this is a closing tag (like `</a>` for `<a>`).
78+ if * c == '/' && tag_name. is_empty ( ) {
7879 is_closing = true ;
7980 } else if c. is_ascii_alphanumeric ( ) && !c. is_ascii_uppercase ( ) {
80- tag_name. push ( c) ;
81+ tag_name. push ( * c) ;
8182 } else {
83+ if !tag_name. is_empty ( ) {
84+ let r = Range { start : range. start + start_pos, end : range. start + pos } ;
85+ if is_closing {
86+ drop_tag ( tags, tag_name, r, f) ;
87+ } else {
88+ tags. push ( ( tag_name, r) ) ;
89+ }
90+ }
8291 break ;
8392 }
8493 iter. next ( ) ;
8594 }
86- if tag_name. is_empty ( ) {
87- // Not an HTML tag presumably...
88- continue ;
89- }
90- if is_closing {
91- drop_tag ( cx, tags, tag_name, hir_id, sp) ;
92- } else {
93- tags. push ( tag_name) ;
94- }
9595 }
9696 }
9797}
@@ -107,26 +107,32 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
107107 } ;
108108 let dox = item. attrs . collapsed_doc_value ( ) . unwrap_or_default ( ) ;
109109 if !dox. is_empty ( ) {
110- let sp = span_of_attrs ( & item. attrs ) . unwrap_or ( item. source . span ( ) ) ;
110+ let cx = & self . cx ;
111+ let report_diag = |msg : & str , range : & Range < usize > | {
112+ let sp = match super :: source_span_for_markdown_range ( cx, & dox, range, & item. attrs ) {
113+ Some ( sp) => sp,
114+ None => span_of_attrs ( & item. attrs ) . unwrap_or ( item. source . span ( ) ) ,
115+ } ;
116+ cx. tcx . struct_span_lint_hir ( lint:: builtin:: INVALID_HTML_TAGS , hir_id, sp, |lint| {
117+ lint. build ( msg) . emit ( )
118+ } ) ;
119+ } ;
120+
111121 let mut tags = Vec :: new ( ) ;
112122
113- let p = Parser :: new_ext ( & dox, opts ( ) ) ;
123+ let p = Parser :: new_ext ( & dox, opts ( ) ) . into_offset_iter ( ) ;
114124
115- for event in p {
125+ for ( event, range ) in p {
116126 match event {
117- Event :: Html ( text) => extract_tag ( self . cx , & mut tags, & text, hir_id , sp ) ,
127+ Event :: Html ( text) => extract_tag ( & mut tags, & text, range , & report_diag ) ,
118128 _ => { }
119129 }
120130 }
121131
122- for tag in tags. iter ( ) . filter ( |t| ALLOWED_UNCLOSED . iter ( ) . find ( |at| at == t) . is_none ( ) )
132+ for ( tag, range) in
133+ tags. iter ( ) . filter ( |( t, _) | ALLOWED_UNCLOSED . iter ( ) . find ( |& at| at == t) . is_none ( ) )
123134 {
124- self . cx . tcx . struct_span_lint_hir (
125- lint:: builtin:: INVALID_HTML_TAGS ,
126- hir_id,
127- sp,
128- |lint| lint. build ( & format ! ( "unclosed HTML tag `{}`" , tag) ) . emit ( ) ,
129- ) ;
135+ report_diag ( & format ! ( "unclosed HTML tag `{}`" , tag) , range) ;
130136 }
131137 }
132138
0 commit comments