@@ -45,14 +45,22 @@ fn drop_tag(
45
45
range : Range < usize > ,
46
46
f : & impl Fn ( & str , & Range < usize > ) ,
47
47
) {
48
- if let Some ( pos) = tags. iter ( ) . rev ( ) . position ( |( t, _) | * t == tag_name) {
48
+ let tag_name_low = tag_name. to_lowercase ( ) ;
49
+ if let Some ( pos) = tags. iter ( ) . rev ( ) . position ( |( t, _) | t. to_lowercase ( ) == tag_name_low) {
49
50
// Because this is from a `rev` iterator, the position is reversed as well!
50
51
let pos = tags. len ( ) - 1 - pos;
51
- // If the tag is nested inside a "<script>", not warning should be emitted.
52
- let should_not_warn =
53
- tags. iter ( ) . take ( pos + 1 ) . any ( |( at, _) | at == "script" || at == "style" ) ;
52
+ // If the tag is nested inside a "<script>" or a "<style>" tag, no warning should
53
+ // be emitted.
54
+ let should_not_warn = tags. iter ( ) . take ( pos + 1 ) . any ( |( at, _) | {
55
+ let at = at. to_lowercase ( ) ;
56
+ at == "script" || at == "style"
57
+ } ) ;
54
58
for ( last_tag_name, last_tag_span) in tags. drain ( pos + 1 ..) {
55
- if should_not_warn || ALLOWED_UNCLOSED . iter ( ) . any ( |& at| at == & last_tag_name) {
59
+ if should_not_warn {
60
+ continue ;
61
+ }
62
+ let last_tag_name_low = last_tag_name. to_lowercase ( ) ;
63
+ if ALLOWED_UNCLOSED . iter ( ) . any ( |& at| at == & last_tag_name_low) {
56
64
continue ;
57
65
}
58
66
// `tags` is used as a queue, meaning that everything after `pos` is included inside it.
@@ -77,21 +85,29 @@ fn extract_tag(
77
85
) {
78
86
let mut iter = text. chars ( ) . enumerate ( ) . peekable ( ) ;
79
87
80
- while let Some ( ( start_pos, c) ) = iter. next ( ) {
88
+ ' top : while let Some ( ( start_pos, c) ) = iter. next ( ) {
81
89
if c == '<' {
82
90
let mut tag_name = String :: new ( ) ;
83
91
let mut is_closing = false ;
84
- while let Some ( ( pos, c) ) = iter. peek ( ) {
92
+ let mut prev_pos = start_pos;
93
+ loop {
94
+ let ( pos, c) = match iter. peek ( ) {
95
+ Some ( ( pos, c) ) => ( * pos, * c) ,
96
+ // In case we reached the of the doc comment, we want to check that it's an
97
+ // unclosed HTML tag. For example "/// <h3".
98
+ None => ( prev_pos, '\0' ) ,
99
+ } ;
100
+ prev_pos = pos;
85
101
// Checking if this is a closing tag (like `</a>` for `<a>`).
86
- if * c == '/' && tag_name. is_empty ( ) {
102
+ if c == '/' && tag_name. is_empty ( ) {
87
103
is_closing = true ;
88
- } else if c. is_ascii_alphanumeric ( ) && !c . is_ascii_uppercase ( ) {
89
- tag_name. push ( * c) ;
104
+ } else if c. is_ascii_alphanumeric ( ) {
105
+ tag_name. push ( c) ;
90
106
} else {
91
107
if !tag_name. is_empty ( ) {
92
108
let mut r =
93
109
Range { start : range. start + start_pos, end : range. start + pos } ;
94
- if * c == '>' {
110
+ if c == '>' {
95
111
// In case we have a tag without attribute, we can consider the span to
96
112
// refer to it fully.
97
113
r. end += 1 ;
@@ -102,11 +118,20 @@ fn extract_tag(
102
118
tags. push ( ( tag_name, r) ) ;
103
119
}
104
120
}
105
- break ;
121
+ continue ' top;
122
+ }
123
+ // Some chars like 💩 are longer than 1 character, so we need to skip the other
124
+ // bytes as well to prevent stopping "in the middle" of a char.
125
+ for _ in 0 ..c. len_utf8 ( ) {
126
+ iter. next ( ) ;
106
127
}
107
- iter. next ( ) ;
108
128
}
109
129
}
130
+ // Some chars like 💩 are longer than 1 character, so we need to skip the other
131
+ // bytes as well to prevent stopping "in the middle" of a char.
132
+ for _ in 0 ..c. len_utf8 ( ) - 1 {
133
+ iter. next ( ) ;
134
+ }
110
135
}
111
136
}
112
137
@@ -143,9 +168,10 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
143
168
}
144
169
}
145
170
146
- for ( tag, range) in
147
- tags. iter ( ) . filter ( |( t, _) | ALLOWED_UNCLOSED . iter ( ) . find ( |& at| at == t) . is_none ( ) )
148
- {
171
+ for ( tag, range) in tags. iter ( ) . filter ( |( t, _) | {
172
+ let t = t. to_lowercase ( ) ;
173
+ ALLOWED_UNCLOSED . iter ( ) . find ( |& & at| at == t) . is_none ( )
174
+ } ) {
149
175
report_diag ( & format ! ( "unclosed HTML tag `{}`" , tag) , range) ;
150
176
}
151
177
}
0 commit comments