@@ -3,10 +3,11 @@ use crate::clean::*;
3
3
use crate :: core:: DocContext ;
4
4
use crate :: fold:: DocFolder ;
5
5
use crate :: html:: markdown:: opts;
6
+ use core:: ops:: Range ;
6
7
use pulldown_cmark:: { Event , Parser } ;
7
- use rustc_hir:: hir_id:: HirId ;
8
+ // use rustc_hir::hir_id::HirId;
8
9
use rustc_session:: lint;
9
- use rustc_span:: Span ;
10
+ // use rustc_span::Span;
10
11
11
12
pub const CHECK_INVALID_HTML_TAGS : Pass = Pass {
12
13
name : "check-invalid-html-tags" ,
@@ -36,62 +37,61 @@ const ALLOWED_UNCLOSED: &[&str] = &[
36
37
] ;
37
38
38
39
fn drop_tag (
39
- cx : & DocContext < ' _ > ,
40
- tags : & mut Vec < String > ,
40
+ tags : & mut Vec < ( String , Range < usize > ) > ,
41
41
tag_name : String ,
42
- hir_id : HirId ,
43
- sp : Span ,
42
+ range : Range < usize > ,
43
+ f : & impl Fn ( & str , & Range < usize > ) ,
44
44
) {
45
- if let Some ( pos) = tags. iter ( ) . position ( |t | * t == tag_name) {
45
+ if let Some ( pos) = tags. iter ( ) . position ( |( t , _ ) | * t == tag_name) {
46
46
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 ( ) {
48
48
continue ;
49
49
}
50
50
// `tags` is used as a queue, meaning that everything after `pos` is included inside it.
51
51
// So `<h2><h3></h2>` will look like `["h2", "h3"]`. So when closing `h2`, we will still
52
52
// 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 ) ;
56
54
tags. remove ( pos + 1 ) ;
57
55
}
58
56
tags. remove ( pos) ;
59
57
} else {
60
58
// It can happen for example in this case: `<h2></script></h2>` (the `h2` tag isn't required
61
59
// 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) ;
65
61
}
66
62
}
67
63
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 ( ) ;
70
71
71
- while let Some ( c ) = iter. next ( ) {
72
+ while let Some ( ( start_pos , c ) ) = iter. next ( ) {
72
73
if c == '<' {
73
74
let mut tag_name = String :: new ( ) ;
74
75
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 ( ) {
78
79
is_closing = true ;
79
80
} else if c. is_ascii_alphanumeric ( ) && !c. is_ascii_uppercase ( ) {
80
- tag_name. push ( c) ;
81
+ tag_name. push ( * c) ;
81
82
} 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
+ }
82
91
break ;
83
92
}
84
93
iter. next ( ) ;
85
94
}
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
- }
95
95
}
96
96
}
97
97
}
@@ -107,26 +107,32 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
107
107
} ;
108
108
let dox = item. attrs . collapsed_doc_value ( ) . unwrap_or_default ( ) ;
109
109
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
+
111
121
let mut tags = Vec :: new ( ) ;
112
122
113
- let p = Parser :: new_ext ( & dox, opts ( ) ) ;
123
+ let p = Parser :: new_ext ( & dox, opts ( ) ) . into_offset_iter ( ) ;
114
124
115
- for event in p {
125
+ for ( event, range ) in p {
116
126
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 ) ,
118
128
_ => { }
119
129
}
120
130
}
121
131
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 ( ) )
123
134
{
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) ;
130
136
}
131
137
}
132
138
0 commit comments