diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs
index b177eaeeb732..0cffaee1c4e9 100644
--- a/src/librustdoc/passes/html_tags.rs
+++ b/src/librustdoc/passes/html_tags.rs
@@ -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,
+ tags: &mut Vec<(String, Range)>,
tag_name: String,
- hir_id: HirId,
- sp: Span,
+ range: Range,
+ f: &impl Fn(&str, &Range),
) {
- 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 `
` 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: `` (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, text: &str, hir_id: HirId, sp: Span) {
- let mut iter = text.chars().peekable();
+fn extract_tag(
+ tags: &mut Vec<(String, Range)>,
+ text: &str,
+ range: Range,
+ f: &impl Fn(&str, &Range),
+) {
+ 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() {
- //
- if c == '/' && tag_name.is_empty() {
+ while let Some((pos, c)) = iter.peek() {
+ // Checking if this is a closing tag (like `` for ``).
+ 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| {
+ 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);
}
}
diff --git a/src/test/rustdoc-ui/invalid-html-tags.rs b/src/test/rustdoc-ui/invalid-html-tags.rs
index 2df7c5435733..b188e16f6051 100644
--- a/src/test/rustdoc-ui/invalid-html-tags.rs
+++ b/src/test/rustdoc-ui/invalid-html-tags.rs
@@ -1,19 +1,22 @@
#![deny(invalid_html_tags)]
+///
///
///
+//~^ ERROR unclosed HTML tag `unknown`
/// < ok
///