Improve invalid_html_tags lint span

This commit is contained in:
Guillaume Gomez 2020-09-24 22:16:51 +02:00
parent bc6ec6fe36
commit 6271a0a46d
3 changed files with 75 additions and 82 deletions

View file

@ -3,10 +3,11 @@ use crate::clean::*;
use crate::core::DocContext;
use crate::fold::DocFolder;
use crate::html::markdown::opts;
use core::ops::Range;
use pulldown_cmark::{Event, Parser};
use rustc_hir::hir_id::HirId;
// use rustc_hir::hir_id::HirId;
use rustc_session::lint;
use rustc_span::Span;
// use rustc_span::Span;
pub const CHECK_INVALID_HTML_TAGS: Pass = Pass {
name: "check-invalid-html-tags",
@ -36,62 +37,61 @@ const ALLOWED_UNCLOSED: &[&str] = &[
];
fn drop_tag(
cx: &DocContext<'_>,
tags: &mut Vec<String>,
tags: &mut Vec<(String, Range<usize>)>,
tag_name: String,
hir_id: HirId,
sp: Span,
range: Range<usize>,
f: &impl Fn(&str, &Range<usize>),
) {
if let Some(pos) = tags.iter().position(|t| *t == tag_name) {
if let Some(pos) = tags.iter().position(|(t, _)| *t == tag_name) {
for _ in pos + 1..tags.len() {
if ALLOWED_UNCLOSED.iter().find(|&at| at == &tags[pos + 1]).is_some() {
if ALLOWED_UNCLOSED.iter().find(|&at| at == &tags[pos + 1].0).is_some() {
continue;
}
// `tags` is used as a queue, meaning that everything after `pos` is included inside it.
// So `<h2><h3></h2>` will look like `["h2", "h3"]`. So when closing `h2`, we will still
// have `h3`, meaning the tag wasn't closed as it should have.
cx.tcx.struct_span_lint_hir(lint::builtin::INVALID_HTML_TAGS, hir_id, sp, |lint| {
lint.build(&format!("unclosed HTML tag `{}`", tags[pos + 1])).emit()
});
f(&format!("unclosed HTML tag `{}`", tags[pos + 1].0), &tags[pos + 1].1);
tags.remove(pos + 1);
}
tags.remove(pos);
} else {
// It can happen for example in this case: `<h2></script></h2>` (the `h2` tag isn't required
// but it helps for the visualization).
cx.tcx.struct_span_lint_hir(lint::builtin::INVALID_HTML_TAGS, hir_id, sp, |lint| {
lint.build(&format!("unopened HTML tag `{}`", tag_name)).emit()
});
f(&format!("unopened HTML tag `{}`", tag_name), &range);
}
}
fn extract_tag(cx: &DocContext<'_>, tags: &mut Vec<String>, text: &str, hir_id: HirId, sp: Span) {
let mut iter = text.chars().peekable();
fn extract_tag(
tags: &mut Vec<(String, Range<usize>)>,
text: &str,
range: Range<usize>,
f: &impl Fn(&str, &Range<usize>),
) {
let mut iter = text.chars().enumerate().peekable();
while let Some(c) = iter.next() {
while let Some((start_pos, c)) = iter.next() {
if c == '<' {
let mut tag_name = String::new();
let mut is_closing = false;
while let Some(&c) = iter.peek() {
// </tag>
if c == '/' && tag_name.is_empty() {
while let Some((pos, c)) = iter.peek() {
// Checking if this is a closing tag (like `</a>` for `<a>`).
if *c == '/' && tag_name.is_empty() {
is_closing = true;
} else if c.is_ascii_alphanumeric() && !c.is_ascii_uppercase() {
tag_name.push(c);
tag_name.push(*c);
} else {
if !tag_name.is_empty() {
let r = Range { start: range.start + start_pos, end: range.start + pos };
if is_closing {
drop_tag(tags, tag_name, r, f);
} else {
tags.push((tag_name, r));
}
}
break;
}
iter.next();
}
if tag_name.is_empty() {
// Not an HTML tag presumably...
continue;
}
if is_closing {
drop_tag(cx, tags, tag_name, hir_id, sp);
} else {
tags.push(tag_name);
}
}
}
}
@ -107,26 +107,32 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
};
let dox = item.attrs.collapsed_doc_value().unwrap_or_default();
if !dox.is_empty() {
let sp = span_of_attrs(&item.attrs).unwrap_or(item.source.span());
let cx = &self.cx;
let report_diag = |msg: &str, range: &Range<usize>| {
let sp = match super::source_span_for_markdown_range(cx, &dox, range, &item.attrs) {
Some(sp) => sp,
None => span_of_attrs(&item.attrs).unwrap_or(item.source.span()),
};
cx.tcx.struct_span_lint_hir(lint::builtin::INVALID_HTML_TAGS, hir_id, sp, |lint| {
lint.build(msg).emit()
});
};
let mut tags = Vec::new();
let p = Parser::new_ext(&dox, opts());
let p = Parser::new_ext(&dox, opts()).into_offset_iter();
for event in p {
for (event, range) in p {
match event {
Event::Html(text) => extract_tag(self.cx, &mut tags, &text, hir_id, sp),
Event::Html(text) => extract_tag(&mut tags, &text, range, &report_diag),
_ => {}
}
}
for tag in tags.iter().filter(|t| ALLOWED_UNCLOSED.iter().find(|at| at == t).is_none())
for (tag, range) in
tags.iter().filter(|(t, _)| ALLOWED_UNCLOSED.iter().find(|&at| at == t).is_none())
{
self.cx.tcx.struct_span_lint_hir(
lint::builtin::INVALID_HTML_TAGS,
hir_id,
sp,
|lint| lint.build(&format!("unclosed HTML tag `{}`", tag)).emit(),
);
report_diag(&format!("unclosed HTML tag `{}`", tag), range);
}
}

View file

@ -1,19 +1,22 @@
#![deny(invalid_html_tags)]
/// <img><input>
/// <script>
//~^ ERROR unclosed HTML tag `unknown`
//~^^ ERROR unclosed HTML tag `script`
/// <img><input>
/// </script>
/// <unknown>
//~^ ERROR unclosed HTML tag `unknown`
/// < ok
/// <script>
//~^ ERROR unclosed HTML tag `script`
pub fn foo() {}
/// <h1>
//~^ ERROR unopened HTML tag `h2`
//~^^ ERROR unopened HTML tag `h3`
/// <h2>
//~^ ERROR unclosed HTML tag `h2`
/// <h3>
//~^ ERROR unclosed HTML tag `h3`
/// </h1>
/// </hello>
//~^ ERROR unopened HTML tag `hello`
pub fn f() {}

View file

@ -1,14 +1,8 @@
error: unclosed HTML tag `unknown`
--> $DIR/invalid-html-tags.rs:3:1
--> $DIR/invalid-html-tags.rs:7:5
|
LL | / /// <script>
LL | |
LL | |
LL | | /// <img><input>
... |
LL | | /// < ok
LL | | /// <script>
| |____________^
LL | /// <unknown>
| ^^^^^^^^
|
note: the lint level is defined here
--> $DIR/invalid-html-tags.rs:1:9
@ -17,38 +11,28 @@ LL | #![deny(invalid_html_tags)]
| ^^^^^^^^^^^^^^^^^
error: unclosed HTML tag `script`
--> $DIR/invalid-html-tags.rs:3:1
--> $DIR/invalid-html-tags.rs:10:5
|
LL | / /// <script>
LL | |
LL | |
LL | | /// <img><input>
... |
LL | | /// < ok
LL | | /// <script>
| |____________^
LL | /// <script>
| ^^^^^^^
error: unopened HTML tag `h2`
--> $DIR/invalid-html-tags.rs:13:1
error: unclosed HTML tag `h2`
--> $DIR/invalid-html-tags.rs:15:7
|
LL | / /// <h1>
LL | |
LL | |
LL | | /// <h2>
LL | | /// <h3>
LL | | /// </h1>
| |_________^
LL | /// <h2>
| ^^^
error: unopened HTML tag `h3`
--> $DIR/invalid-html-tags.rs:13:1
error: unclosed HTML tag `h3`
--> $DIR/invalid-html-tags.rs:17:9
|
LL | / /// <h1>
LL | |
LL | |
LL | | /// <h2>
LL | | /// <h3>
LL | | /// </h1>
| |_________^
LL | /// <h3>
| ^^^
error: aborting due to 4 previous errors
error: unopened HTML tag `hello`
--> $DIR/invalid-html-tags.rs:20:5
|
LL | /// </hello>
| ^^^^^^^
error: aborting due to 5 previous errors