about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGuillaume Gomez <guillaume1.gomez@gmail.com>2020-09-24 22:16:51 +0200
committerGuillaume Gomez <guillaume1.gomez@gmail.com>2020-10-03 14:16:24 +0200
commit6271a0a46dedd5eaf3eb26afb6ae536939496cd7 (patch)
tree71cceea837c95eb57efbff24a01edd89c421d7fb
parentbc6ec6fe36c5c902da900fea67cba30fad4b0b6b (diff)
downloadrust-6271a0a46dedd5eaf3eb26afb6ae536939496cd7.tar.gz
rust-6271a0a46dedd5eaf3eb26afb6ae536939496cd7.zip
Improve invalid_html_tags lint span
-rw-r--r--src/librustdoc/passes/html_tags.rs88
-rw-r--r--src/test/rustdoc-ui/invalid-html-tags.rs11
-rw-r--r--src/test/rustdoc-ui/invalid-html-tags.stderr58
3 files changed, 75 insertions, 82 deletions
diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs
index b177eaeeb73..0cffaee1c4e 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<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);
             }
         }
 
diff --git a/src/test/rustdoc-ui/invalid-html-tags.rs b/src/test/rustdoc-ui/invalid-html-tags.rs
index 2df7c543573..b188e16f605 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)]
 
+/// <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() {}
diff --git a/src/test/rustdoc-ui/invalid-html-tags.stderr b/src/test/rustdoc-ui/invalid-html-tags.stderr
index f022e0168eb..f8e67732f63 100644
--- a/src/test/rustdoc-ui/invalid-html-tags.stderr
+++ b/src/test/rustdoc-ui/invalid-html-tags.stderr
@@ -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