@@ -7,6 +7,8 @@ use core::ops::Range;
77use pulldown_cmark:: { Event , Parser } ;
88use rustc_feature:: UnstableFeatures ;
99use rustc_session:: lint;
10+ use std:: iter:: Peekable ;
11+ use std:: str:: CharIndices ;
1012
1113pub const CHECK_INVALID_HTML_TAGS : Pass = Pass {
1214 name : "check-invalid-html-tags" ,
@@ -75,70 +77,97 @@ fn drop_tag(
7577 }
7678}
7779
78- fn extract_tag (
80+ fn extract_html_tag (
7981 tags : & mut Vec < ( String , Range < usize > ) > ,
8082 text : & str ,
81- range : Range < usize > ,
83+ range : & Range < usize > ,
84+ start_pos : usize ,
85+ iter : & mut Peekable < CharIndices < ' _ > > ,
8286 f : & impl Fn ( & str , & Range < usize > ) ,
8387) {
84- let mut iter = text. char_indices ( ) . peekable ( ) ;
88+ let mut tag_name = String :: new ( ) ;
89+ let mut is_closing = false ;
90+ let mut prev_pos = start_pos;
8591
86- while let Some ( ( start_pos, c) ) = iter. next ( ) {
87- if c == '<' {
88- let mut tag_name = String :: new ( ) ;
89- let mut is_closing = false ;
90- let mut prev_pos = start_pos;
91- loop {
92- let ( pos, c) = match iter. peek ( ) {
93- Some ( ( pos, c) ) => ( * pos, * c) ,
94- // In case we reached the of the doc comment, we want to check that it's an
95- // unclosed HTML tag. For example "/// <h3".
96- None => ( prev_pos, '\0' ) ,
97- } ;
98- prev_pos = pos;
99- // Checking if this is a closing tag (like `</a>` for `<a>`).
100- if c == '/' && tag_name. is_empty ( ) {
101- is_closing = true ;
102- } else if c. is_ascii_alphanumeric ( ) {
103- tag_name. push ( c) ;
104- } else {
105- if !tag_name. is_empty ( ) {
106- let mut r =
107- Range { start : range. start + start_pos, end : range. start + pos } ;
108- if c == '>' {
109- // In case we have a tag without attribute, we can consider the span to
110- // refer to it fully.
111- r. end += 1 ;
92+ loop {
93+ let ( pos, c) = match iter. peek ( ) {
94+ Some ( ( pos, c) ) => ( * pos, * c) ,
95+ // In case we reached the of the doc comment, we want to check that it's an
96+ // unclosed HTML tag. For example "/// <h3".
97+ None => ( prev_pos, '\0' ) ,
98+ } ;
99+ prev_pos = pos;
100+ // Checking if this is a closing tag (like `</a>` for `<a>`).
101+ if c == '/' && tag_name. is_empty ( ) {
102+ is_closing = true ;
103+ } else if c. is_ascii_alphanumeric ( ) {
104+ tag_name. push ( c) ;
105+ } else {
106+ if !tag_name. is_empty ( ) {
107+ let mut r = Range { start : range. start + start_pos, end : range. start + pos } ;
108+ if c == '>' {
109+ // In case we have a tag without attribute, we can consider the span to
110+ // refer to it fully.
111+ r. end += 1 ;
112+ }
113+ if is_closing {
114+ // In case we have "</div >" or even "</div >".
115+ if c != '>' {
116+ if !c. is_whitespace ( ) {
117+ // It seems like it's not a valid HTML tag.
118+ break ;
112119 }
113- if is_closing {
114- // In case we have "</div >" or even "</div >".
115- if c != '>' {
116- if !c. is_whitespace ( ) {
117- // It seems like it's not a valid HTML tag.
118- break ;
119- }
120- let mut found = false ;
121- for ( new_pos, c) in text[ pos..] . char_indices ( ) {
122- if !c. is_whitespace ( ) {
123- if c == '>' {
124- r. end = range. start + new_pos + 1 ;
125- found = true ;
126- }
127- break ;
128- }
129- }
130- if !found {
131- break ;
120+ let mut found = false ;
121+ for ( new_pos, c) in text[ pos..] . char_indices ( ) {
122+ if !c. is_whitespace ( ) {
123+ if c == '>' {
124+ r. end = range. start + new_pos + 1 ;
125+ found = true ;
132126 }
127+ break ;
133128 }
134- drop_tag ( tags , tag_name , r , f ) ;
135- } else {
136- tags . push ( ( tag_name , r ) ) ;
129+ }
130+ if !found {
131+ break ;
137132 }
138133 }
139- break ;
134+ drop_tag ( tags, tag_name, r, f) ;
135+ } else {
136+ tags. push ( ( tag_name, r) ) ;
140137 }
138+ }
139+ break ;
140+ }
141+ iter. next ( ) ;
142+ }
143+ }
144+
145+ fn extract_tags (
146+ tags : & mut Vec < ( String , Range < usize > ) > ,
147+ text : & str ,
148+ range : Range < usize > ,
149+ is_in_comment : & mut Option < Range < usize > > ,
150+ f : & impl Fn ( & str , & Range < usize > ) ,
151+ ) {
152+ let mut iter = text. char_indices ( ) . peekable ( ) ;
153+
154+ while let Some ( ( start_pos, c) ) = iter. next ( ) {
155+ if is_in_comment. is_some ( ) {
156+ if text[ start_pos..] . starts_with ( "-->" ) {
157+ * is_in_comment = None ;
158+ }
159+ } else if c == '<' {
160+ if text[ start_pos..] . starts_with ( "<!--" ) {
161+ // We skip the "!--" part. (Once `advance_by` is stable, might be nice to use it!)
162+ iter. next ( ) ;
141163 iter. next ( ) ;
164+ iter. next ( ) ;
165+ * is_in_comment = Some ( Range {
166+ start : range. start + start_pos,
167+ end : range. start + start_pos + 3 ,
168+ } ) ;
169+ } else {
170+ extract_html_tag ( tags, text, & range, start_pos, & mut iter, f) ;
142171 }
143172 }
144173 }
@@ -167,12 +196,15 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
167196 } ;
168197
169198 let mut tags = Vec :: new ( ) ;
199+ let mut is_in_comment = None ;
170200
171201 let p = Parser :: new_ext ( & dox, opts ( ) ) . into_offset_iter ( ) ;
172202
173203 for ( event, range) in p {
174204 match event {
175- Event :: Html ( text) => extract_tag ( & mut tags, & text, range, & report_diag) ,
205+ Event :: Html ( text) | Event :: Text ( text) => {
206+ extract_tags ( & mut tags, & text, range, & mut is_in_comment, & report_diag)
207+ }
176208 _ => { }
177209 }
178210 }
@@ -183,6 +215,10 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
183215 } ) {
184216 report_diag ( & format ! ( "unclosed HTML tag `{}`" , tag) , range) ;
185217 }
218+
219+ if let Some ( range) = is_in_comment {
220+ report_diag ( "Unclosed HTML comment" , & range) ;
221+ }
186222 }
187223
188224 self . fold_item_recur ( item)
0 commit comments