Skip to content

Commit 6271a0a

Browse files
Improve invalid_html_tags lint span
1 parent bc6ec6f commit 6271a0a

File tree

3 files changed

+75
-82
lines changed

3 files changed

+75
-82
lines changed

src/librustdoc/passes/html_tags.rs

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ use crate::clean::*;
33
use crate::core::DocContext;
44
use crate::fold::DocFolder;
55
use crate::html::markdown::opts;
6+
use core::ops::Range;
67
use pulldown_cmark::{Event, Parser};
7-
use rustc_hir::hir_id::HirId;
8+
// use rustc_hir::hir_id::HirId;
89
use rustc_session::lint;
9-
use rustc_span::Span;
10+
// use rustc_span::Span;
1011

1112
pub const CHECK_INVALID_HTML_TAGS: Pass = Pass {
1213
name: "check-invalid-html-tags",
@@ -36,62 +37,61 @@ const ALLOWED_UNCLOSED: &[&str] = &[
3637
];
3738

3839
fn 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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
#![deny(invalid_html_tags)]
22

3+
/// <img><input>
34
/// <script>
4-
//~^ ERROR unclosed HTML tag `unknown`
5-
//~^^ ERROR unclosed HTML tag `script`
65
/// <img><input>
76
/// </script>
87
/// <unknown>
8+
//~^ ERROR unclosed HTML tag `unknown`
99
/// < ok
1010
/// <script>
11+
//~^ ERROR unclosed HTML tag `script`
1112
pub fn foo() {}
1213

1314
/// <h1>
14-
//~^ ERROR unopened HTML tag `h2`
15-
//~^^ ERROR unopened HTML tag `h3`
1615
/// <h2>
16+
//~^ ERROR unclosed HTML tag `h2`
1717
/// <h3>
18+
//~^ ERROR unclosed HTML tag `h3`
1819
/// </h1>
20+
/// </hello>
21+
//~^ ERROR unopened HTML tag `hello`
1922
pub fn f() {}
Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
error: unclosed HTML tag `unknown`
2-
--> $DIR/invalid-html-tags.rs:3:1
2+
--> $DIR/invalid-html-tags.rs:7:5
33
|
4-
LL | / /// <script>
5-
LL | |
6-
LL | |
7-
LL | | /// <img><input>
8-
... |
9-
LL | | /// < ok
10-
LL | | /// <script>
11-
| |____________^
4+
LL | /// <unknown>
5+
| ^^^^^^^^
126
|
137
note: the lint level is defined here
148
--> $DIR/invalid-html-tags.rs:1:9
@@ -17,38 +11,28 @@ LL | #![deny(invalid_html_tags)]
1711
| ^^^^^^^^^^^^^^^^^
1812

1913
error: unclosed HTML tag `script`
20-
--> $DIR/invalid-html-tags.rs:3:1
14+
--> $DIR/invalid-html-tags.rs:10:5
2115
|
22-
LL | / /// <script>
23-
LL | |
24-
LL | |
25-
LL | | /// <img><input>
26-
... |
27-
LL | | /// < ok
28-
LL | | /// <script>
29-
| |____________^
16+
LL | /// <script>
17+
| ^^^^^^^
3018

31-
error: unopened HTML tag `h2`
32-
--> $DIR/invalid-html-tags.rs:13:1
19+
error: unclosed HTML tag `h2`
20+
--> $DIR/invalid-html-tags.rs:15:7
3321
|
34-
LL | / /// <h1>
35-
LL | |
36-
LL | |
37-
LL | | /// <h2>
38-
LL | | /// <h3>
39-
LL | | /// </h1>
40-
| |_________^
22+
LL | /// <h2>
23+
| ^^^
4124

42-
error: unopened HTML tag `h3`
43-
--> $DIR/invalid-html-tags.rs:13:1
25+
error: unclosed HTML tag `h3`
26+
--> $DIR/invalid-html-tags.rs:17:9
4427
|
45-
LL | / /// <h1>
46-
LL | |
47-
LL | |
48-
LL | | /// <h2>
49-
LL | | /// <h3>
50-
LL | | /// </h1>
51-
| |_________^
28+
LL | /// <h3>
29+
| ^^^
5230

53-
error: aborting due to 4 previous errors
31+
error: unopened HTML tag `hello`
32+
--> $DIR/invalid-html-tags.rs:20:5
33+
|
34+
LL | /// </hello>
35+
| ^^^^^^^
36+
37+
error: aborting due to 5 previous errors
5438

0 commit comments

Comments
 (0)