diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs
index ae4eac89b457a..1d9be619ec91a 100644
--- a/src/librustdoc/passes/html_tags.rs
+++ b/src/librustdoc/passes/html_tags.rs
@@ -7,6 +7,8 @@ use core::ops::Range;
 use pulldown_cmark::{Event, Parser};
 use rustc_feature::UnstableFeatures;
 use rustc_session::lint;
+use std::iter::Peekable;
+use std::str::CharIndices;
 
 pub const CHECK_INVALID_HTML_TAGS: Pass = Pass {
     name: "check-invalid-html-tags",
@@ -75,70 +77,97 @@ fn drop_tag(
     }
 }
 
-fn extract_tag(
+fn extract_html_tag(
     tags: &mut Vec<(String, Range<usize>)>,
     text: &str,
-    range: Range<usize>,
+    range: &Range<usize>,
+    start_pos: usize,
+    iter: &mut Peekable<CharIndices<'_>>,
     f: &impl Fn(&str, &Range<usize>),
 ) {
-    let mut iter = text.char_indices().peekable();
+    let mut tag_name = String::new();
+    let mut is_closing = false;
+    let mut prev_pos = start_pos;
 
-    while let Some((start_pos, c)) = iter.next() {
-        if c == '<' {
-            let mut tag_name = String::new();
-            let mut is_closing = false;
-            let mut prev_pos = start_pos;
-            loop {
-                let (pos, c) = match iter.peek() {
-                    Some((pos, c)) => (*pos, *c),
-                    // In case we reached the of the doc comment, we want to check that it's an
-                    // unclosed HTML tag. For example "/// <h3".
-                    None => (prev_pos, '\0'),
-                };
-                prev_pos = pos;
-                // 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() {
-                    tag_name.push(c);
-                } else {
-                    if !tag_name.is_empty() {
-                        let mut r =
-                            Range { start: range.start + start_pos, end: range.start + pos };
-                        if c == '>' {
-                            // In case we have a tag without attribute, we can consider the span to
-                            // refer to it fully.
-                            r.end += 1;
+    loop {
+        let (pos, c) = match iter.peek() {
+            Some((pos, c)) => (*pos, *c),
+            // In case we reached the of the doc comment, we want to check that it's an
+            // unclosed HTML tag. For example "/// <h3".
+            None => (prev_pos, '\0'),
+        };
+        prev_pos = pos;
+        // 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() {
+            tag_name.push(c);
+        } else {
+            if !tag_name.is_empty() {
+                let mut r = Range { start: range.start + start_pos, end: range.start + pos };
+                if c == '>' {
+                    // In case we have a tag without attribute, we can consider the span to
+                    // refer to it fully.
+                    r.end += 1;
+                }
+                if is_closing {
+                    // In case we have "</div >" or even "</div         >".
+                    if c != '>' {
+                        if !c.is_whitespace() {
+                            // It seems like it's not a valid HTML tag.
+                            break;
                         }
-                        if is_closing {
-                            // In case we have "</div >" or even "</div         >".
-                            if c != '>' {
-                                if !c.is_whitespace() {
-                                    // It seems like it's not a valid HTML tag.
-                                    break;
-                                }
-                                let mut found = false;
-                                for (new_pos, c) in text[pos..].char_indices() {
-                                    if !c.is_whitespace() {
-                                        if c == '>' {
-                                            r.end = range.start + new_pos + 1;
-                                            found = true;
-                                        }
-                                        break;
-                                    }
-                                }
-                                if !found {
-                                    break;
+                        let mut found = false;
+                        for (new_pos, c) in text[pos..].char_indices() {
+                            if !c.is_whitespace() {
+                                if c == '>' {
+                                    r.end = range.start + new_pos + 1;
+                                    found = true;
                                 }
+                                break;
                             }
-                            drop_tag(tags, tag_name, r, f);
-                        } else {
-                            tags.push((tag_name, r));
+                        }
+                        if !found {
+                            break;
                         }
                     }
-                    break;
+                    drop_tag(tags, tag_name, r, f);
+                } else {
+                    tags.push((tag_name, r));
                 }
+            }
+            break;
+        }
+        iter.next();
+    }
+}
+
+fn extract_tags(
+    tags: &mut Vec<(String, Range<usize>)>,
+    text: &str,
+    range: Range<usize>,
+    is_in_comment: &mut Option<Range<usize>>,
+    f: &impl Fn(&str, &Range<usize>),
+) {
+    let mut iter = text.char_indices().peekable();
+
+    while let Some((start_pos, c)) = iter.next() {
+        if is_in_comment.is_some() {
+            if text[start_pos..].starts_with("-->") {
+                *is_in_comment = None;
+            }
+        } else if c == '<' {
+            if text[start_pos..].starts_with("<!--") {
+                // We skip the "!--" part. (Once `advance_by` is stable, might be nice to use it!)
+                iter.next();
                 iter.next();
+                iter.next();
+                *is_in_comment = Some(Range {
+                    start: range.start + start_pos,
+                    end: range.start + start_pos + 3,
+                });
+            } else {
+                extract_html_tag(tags, text, &range, start_pos, &mut iter, f);
             }
         }
     }
@@ -167,12 +196,15 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
             };
 
             let mut tags = Vec::new();
+            let mut is_in_comment = None;
 
             let p = Parser::new_ext(&dox, opts()).into_offset_iter();
 
             for (event, range) in p {
                 match event {
-                    Event::Html(text) => extract_tag(&mut tags, &text, range, &report_diag),
+                    Event::Html(text) | Event::Text(text) => {
+                        extract_tags(&mut tags, &text, range, &mut is_in_comment, &report_diag)
+                    }
                     _ => {}
                 }
             }
@@ -183,6 +215,10 @@ impl<'a, 'tcx> DocFolder for InvalidHtmlTagsLinter<'a, 'tcx> {
             }) {
                 report_diag(&format!("unclosed HTML tag `{}`", tag), range);
             }
+
+            if let Some(range) = is_in_comment {
+                report_diag("Unclosed HTML comment", &range);
+            }
         }
 
         self.fold_item_recur(item)
diff --git a/src/test/rustdoc-ui/invalid-html-tags.rs b/src/test/rustdoc-ui/invalid-html-tags.rs
index d878e390ca339..56ca7e79d4358 100644
--- a/src/test/rustdoc-ui/invalid-html-tags.rs
+++ b/src/test/rustdoc-ui/invalid-html-tags.rs
@@ -73,3 +73,17 @@ pub fn e() {}
 /// <div></div
 //~^ ERROR unclosed HTML tag `div`
 pub fn f() {}
+
+/// <!---->
+/// <!-- -->
+/// <!-- <div> -->
+/// <!-- <!-- -->
+pub fn g() {}
+
+/// <!--
+/// -->
+pub fn h() {}
+
+/// <!--
+//~^ ERROR Unclosed HTML comment
+pub fn i() {}
diff --git a/src/test/rustdoc-ui/invalid-html-tags.stderr b/src/test/rustdoc-ui/invalid-html-tags.stderr
index 11f176ff05cc6..aa9ace006bd1a 100644
--- a/src/test/rustdoc-ui/invalid-html-tags.stderr
+++ b/src/test/rustdoc-ui/invalid-html-tags.stderr
@@ -76,5 +76,11 @@ error: unclosed HTML tag `div`
 LL | /// <div></div
    |     ^^^^^
 
-error: aborting due to 12 previous errors
+error: Unclosed HTML comment
+  --> $DIR/invalid-html-tags.rs:87:5
+   |
+LL | /// <!--
+   |     ^^^
+
+error: aborting due to 13 previous errors