Improve invalid_html_tags lint span
This commit is contained in:
parent
bc6ec6fe36
commit
6271a0a46d
3 changed files with 75 additions and 82 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue