@@ -7,6 +7,8 @@ use core::ops::Range;
7
7
use pulldown_cmark:: { Event , Parser } ;
8
8
use rustc_feature:: UnstableFeatures ;
9
9
use rustc_session:: lint;
10
+ use std:: iter:: Peekable ;
11
+ use std:: str:: CharIndices ;
10
12
11
13
pub const CHECK_INVALID_HTML_TAGS : Pass = Pass {
12
14
name : "check-invalid-html-tags" ,
@@ -75,70 +77,97 @@ fn drop_tag(
75
77
}
76
78
}
77
79
78
- fn extract_tag (
80
+ fn extract_html_tag (
79
81
tags : & mut Vec < ( String , Range < usize > ) > ,
80
82
text : & str ,
81
- range : Range < usize > ,
83
+ range : & Range < usize > ,
84
+ start_pos : usize ,
85
+ iter : & mut Peekable < CharIndices < ' _ > > ,
82
86
f : & impl Fn ( & str , & Range < usize > ) ,
83
87
) {
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;
85
91
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 ;
112
119
}
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 ;
132
126
}
127
+ break ;
133
128
}
134
- drop_tag ( tags , tag_name , r , f ) ;
135
- } else {
136
- tags . push ( ( tag_name , r ) ) ;
129
+ }
130
+ if !found {
131
+ break ;
137
132
}
138
133
}
139
- break ;
134
+ drop_tag ( tags, tag_name, r, f) ;
135
+ } else {
136
+ tags. push ( ( tag_name, r) ) ;
140
137
}
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 ( ) ;
141
163
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) ;
142
171
}
143
172
}
144
173
}
@@ -167,12 +196,15 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
167
196
} ;
168
197
169
198
let mut tags = Vec :: new ( ) ;
199
+ let mut is_in_comment = None ;
170
200
171
201
let p = Parser :: new_ext ( & dox, opts ( ) ) . into_offset_iter ( ) ;
172
202
173
203
for ( event, range) in p {
174
204
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
+ }
176
208
_ => { }
177
209
}
178
210
}
@@ -183,6 +215,10 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
183
215
} ) {
184
216
report_diag ( & format ! ( "unclosed HTML tag `{}`" , tag) , range) ;
185
217
}
218
+
219
+ if let Some ( range) = is_in_comment {
220
+ report_diag ( "Unclosed HTML comment" , & range) ;
221
+ }
186
222
}
187
223
188
224
self . fold_item_recur ( item)
0 commit comments