From 214ce5702c2f1eb5c098e5e393df09c3b1829572 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 1 Feb 2022 22:22:57 -0700 Subject: [PATCH 1/4] rustdoc: correct unclosed HTML tags as generics --- src/librustdoc/lib.rs | 1 + src/librustdoc/passes/html_tags.rs | 81 ++++++++++++++++--- .../html-as-generics-no-suggestions.rs | 38 +++++++++ .../html-as-generics-no-suggestions.stderr | 38 +++++++++ .../suggestions/html-as-generics.fixed | 38 +++++++++ .../suggestions/html-as-generics.rs | 38 +++++++++ .../suggestions/html-as-generics.stderr | 56 +++++++++++++ 7 files changed, 281 insertions(+), 9 deletions(-) create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.rs create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.stderr create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics.fixed create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics.rs create mode 100644 src/test/rustdoc-ui/suggestions/html-as-generics.stderr diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index a7c3c0bb60610..68028604fa463 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -5,6 +5,7 @@ #![feature(rustc_private)] #![feature(array_methods)] #![feature(assert_matches)] +#![feature(bool_to_option)] #![feature(box_patterns)] #![feature(control_flow_enum)] #![feature(box_syntax)] diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index f7a9a0899e390..7ad14bbcbb16f 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -38,7 +38,7 @@ fn drop_tag( tags: &mut Vec<(String, Range)>, tag_name: String, range: Range, - f: &impl Fn(&str, &Range), + f: &impl Fn(&str, &Range, bool), ) { let tag_name_low = tag_name.to_lowercase(); if let Some(pos) = tags.iter().rposition(|(t, _)| t.to_lowercase() == tag_name_low) { @@ -59,14 +59,42 @@ fn drop_tag( // `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. - f(&format!("unclosed HTML tag `{}`", last_tag_name), &last_tag_span); + f(&format!("unclosed HTML tag `{}`", last_tag_name), &last_tag_span, true); } // Remove the `tag_name` that was originally closed tags.pop(); } else { // It can happen for example in this case: `

` (the `h2` tag isn't required // but it helps for the visualization). - f(&format!("unopened HTML tag `{}`", tag_name), &range); + f(&format!("unopened HTML tag `{}`", tag_name), &range, false); + } +} + +fn extract_path_backwards(text: &str, end_pos: usize) -> Option { + use rustc_lexer::{is_id_continue, is_id_start}; + let mut current_pos = end_pos; + loop { + if current_pos >= 2 && &text[current_pos - 2..current_pos] == "::" { + current_pos -= 2; + } + let new_pos = text[..current_pos] + .char_indices() + .rev() + .take_while(|(_, c)| is_id_start(*c) || is_id_continue(*c)) + .reduce(|_accum, item| item) + .and_then(|(new_pos, c)| is_id_start(c).then_some(new_pos)); + if let Some(new_pos) = new_pos { + if current_pos != new_pos { + current_pos = new_pos; + continue; + } + } + break; + } + if current_pos == end_pos { + return None; + } else { + return Some(current_pos); } } @@ -76,7 +104,7 @@ fn extract_html_tag( range: &Range, start_pos: usize, iter: &mut Peekable>, - f: &impl Fn(&str, &Range), + f: &impl Fn(&str, &Range, bool), ) { let mut tag_name = String::new(); let mut is_closing = false; @@ -140,7 +168,7 @@ fn extract_tags( text: &str, range: Range, is_in_comment: &mut Option>, - f: &impl Fn(&str, &Range), + f: &impl Fn(&str, &Range, bool), ) { let mut iter = text.char_indices().peekable(); @@ -178,14 +206,49 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { }; let dox = item.attrs.collapsed_doc_value().unwrap_or_default(); if !dox.is_empty() { - let report_diag = |msg: &str, range: &Range| { + let report_diag = |msg: &str, range: &Range, is_open_tag: bool| { let sp = match super::source_span_for_markdown_range(tcx, &dox, range, &item.attrs) { Some(sp) => sp, None => item.attr_span(tcx), }; tcx.struct_span_lint_hir(crate::lint::INVALID_HTML_TAGS, hir_id, sp, |lint| { - lint.build(msg).emit() + use rustc_lint_defs::Applicability; + let mut diag = lint.build(msg); + // If a tag looks like ``, it might actually be a generic. + // We don't try to detect stuff `` because that's not valid HTML, + // and we don' try to detect stuff `` because that's not valid Rust. + if let Some(Some(generics_start)) = (is_open_tag + && &dox[range.end - 1..range.end] == ">") + .then(|| extract_path_backwards(&dox, range.start)) + { + let generics_sp = match super::source_span_for_markdown_range( + tcx, + &dox, + &(generics_start..range.end), + &item.attrs, + ) { + Some(sp) => sp, + None => item.attr_span(tcx), + }; + if let Ok(generics_snippet) = + tcx.sess.source_map().span_to_snippet(generics_sp) + { + // short form is chosen here because ``Vec`` would be confusing. + diag.span_suggestion_short( + generics_sp, + "try marking as source code with `backticks`", + format!("`{}`", generics_snippet), + Applicability::MachineApplicable, + ); + } else { + diag.span_help( + generics_sp, + "try marking as source code with `backticks`", + ); + } + } + diag.emit() }); }; @@ -210,11 +273,11 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { let t = t.to_lowercase(); !ALLOWED_UNCLOSED.contains(&t.as_str()) }) { - report_diag(&format!("unclosed HTML tag `{}`", tag), range); + report_diag(&format!("unclosed HTML tag `{}`", tag), range, true); } if let Some(range) = is_in_comment { - report_diag("Unclosed HTML comment", &range); + report_diag("Unclosed HTML comment", &range, false); } } diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.rs b/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.rs new file mode 100644 index 0000000000000..744b3071f1b81 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.rs @@ -0,0 +1,38 @@ +#![deny(rustdoc::invalid_html_tags)] + +/// This Vec<32> thing! +// Numbers aren't valid HTML tags, so no error. +pub struct ConstGeneric; + +/// This Vec thing! +// HTML tags cannot contain commas, so no error. +pub struct MultipleGenerics; + +/// This Vec thing! +//~^ERROR unclosed HTML tag `i32` +// HTML attributes shouldn't be treated as Rust syntax, so no suggestions. +pub struct TagWithAttributes; + +/// This Vec thing! +// There should be no error, and no suggestion, since the tags are balanced. +pub struct DoNotWarnOnMatchingTags; + +/// This Vec thing! +//~^ERROR unopened HTML tag `i32` +// This should produce an error, but no suggestion. +pub struct EndTagsAreNotValidRustSyntax; + +/// This 123 thing! +//~^ERROR unclosed HTML tag `i32` +// This should produce an error, but no suggestion. +pub struct NumbersAreNotPaths; + +/// This Vec: thing! +//~^ERROR unclosed HTML tag `i32` +// This should produce an error, but no suggestion. +pub struct InvalidTurbofish; + +/// This [link](https://rust-lang.org) thing! +//~^ERROR unclosed HTML tag `i32` +// This should produce an error, but no suggestion. +pub struct BareTurbofish; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.stderr b/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.stderr new file mode 100644 index 0000000000000..832b8b2cac79a --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics-no-suggestions.stderr @@ -0,0 +1,38 @@ +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:11:13 + | +LL | /// This Vec thing! + | ^^^^ + | +note: the lint level is defined here + --> $DIR/html-as-generics-no-suggestions.rs:1:9 + | +LL | #![deny(rustdoc::invalid_html_tags)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unopened HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:20:13 + | +LL | /// This Vec thing! + | ^^^^^^ + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:25:13 + | +LL | /// This 123 thing! + | ^^^^^ + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:30:14 + | +LL | /// This Vec: thing! + | ^^^^^ + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics-no-suggestions.rs:35:39 + | +LL | /// This [link](https://rust-lang.org) thing! + | ^^^^^ + +error: aborting due to 5 previous errors + diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.fixed b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed new file mode 100644 index 0000000000000..04bdd038993a4 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed @@ -0,0 +1,38 @@ +// run-rustfix +#![deny(rustdoc::invalid_html_tags)] + +/// This `Vec` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec` +pub struct Generic; + +/// This `vec::Vec` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `vec::Vec` +pub struct GenericPath; + +/// This `i32` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `i32` +pub struct PathsCanContainTrailingNumbers; + +/// This `Vec::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec::` +pub struct Turbofish; + +/// This [link](https://rust-lang.org)`::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `::` +pub struct BareTurbofish; + +/// This `Vec::` thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec::` +pub struct Nested; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.rs b/src/test/rustdoc-ui/suggestions/html-as-generics.rs new file mode 100644 index 0000000000000..28e50c0073851 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.rs @@ -0,0 +1,38 @@ +// run-rustfix +#![deny(rustdoc::invalid_html_tags)] + +/// This Vec thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec` +pub struct Generic; + +/// This vec::Vec thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `vec::Vec` +pub struct GenericPath; + +/// This i32 thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `i32` +pub struct PathsCanContainTrailingNumbers; + +/// This Vec:: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec::` +pub struct Turbofish; + +/// This [link](https://rust-lang.org):: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `::` +pub struct BareTurbofish; + +/// This Vec:: thing! +//~^ERROR unclosed HTML tag `i32` +//~|HELP try marking as source +//~|SUGGESTION `Vec::` +pub struct Nested; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.stderr b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr new file mode 100644 index 0000000000000..c0a1603bc6652 --- /dev/null +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr @@ -0,0 +1,56 @@ +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:4:13 + | +LL | /// This Vec thing! + | ---^^^^^ + | | + | help: try marking as source code with `backticks` + | +note: the lint level is defined here + --> $DIR/html-as-generics.rs:2:9 + | +LL | #![deny(rustdoc::invalid_html_tags)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:10:18 + | +LL | /// This vec::Vec thing! + | --------^^^^^ + | | + | help: try marking as source code with `backticks` + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:16:13 + | +LL | /// This i32 thing! + | ---^^^^^ + | | + | help: try marking as source code with `backticks` + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:22:15 + | +LL | /// This Vec:: thing! + | -----^^^^^ + | | + | help: try marking as source code with `backticks` + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:28:41 + | +LL | /// This [link](https://rust-lang.org):: thing! + | --^^^^^ + | | + | help: try marking as source code with `backticks` + +error: unclosed HTML tag `i32` + --> $DIR/html-as-generics.rs:34:21 + | +LL | /// This Vec:: thing! + | -----^^^^^ + | | + | help: try marking as source code with `backticks` + +error: aborting due to 6 previous errors + From c9907ad853f9d4837f62c3a00f0d33fba562d8e3 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 1 Feb 2022 23:01:44 -0700 Subject: [PATCH 2/4] In retrospect, MachineApplicable is probably too confident --- src/librustdoc/passes/html_tags.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index 7ad14bbcbb16f..24fc97f234149 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -239,7 +239,7 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { generics_sp, "try marking as source code with `backticks`", format!("`{}`", generics_snippet), - Applicability::MachineApplicable, + Applicability::MaybeIncorrect, ); } else { diag.span_help( From 0db9e4067038f3c70eb04e86b1bba5b14b172f47 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 1 Feb 2022 23:06:13 -0700 Subject: [PATCH 3/4] Fix unicode slicing bug --- src/librustdoc/passes/html_tags.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index 24fc97f234149..f6e7ec8094e45 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -74,7 +74,7 @@ fn extract_path_backwards(text: &str, end_pos: usize) -> Option { use rustc_lexer::{is_id_continue, is_id_start}; let mut current_pos = end_pos; loop { - if current_pos >= 2 && &text[current_pos - 2..current_pos] == "::" { + if current_pos >= 2 && text[..current_pos].ends_with("::") { current_pos -= 2; } let new_pos = text[..current_pos] @@ -217,9 +217,9 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { let mut diag = lint.build(msg); // If a tag looks like ``, it might actually be a generic. // We don't try to detect stuff `` because that's not valid HTML, - // and we don' try to detect stuff `` because that's not valid Rust. + // and we don't try to detect stuff `` because that's not valid Rust. if let Some(Some(generics_start)) = (is_open_tag - && &dox[range.end - 1..range.end] == ">") + && dox[..range.end].ends_with(">")) .then(|| extract_path_backwards(&dox, range.start)) { let generics_sp = match super::source_span_for_markdown_range( From 76b5b27d88234d60a1609b86445b134c81b61cf2 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Tue, 1 Feb 2022 23:27:07 -0700 Subject: [PATCH 4/4] Use multipart suggestion for code wrapping Another one of those "good grief, I just submitted it and NOW I think of it" moments. --- src/librustdoc/passes/html_tags.rs | 27 +++----- .../suggestions/html-as-generics.fixed | 6 -- .../suggestions/html-as-generics.rs | 6 -- .../suggestions/html-as-generics.stderr | 63 ++++++++++++------- 4 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/librustdoc/passes/html_tags.rs b/src/librustdoc/passes/html_tags.rs index f6e7ec8094e45..9caadef3dec7c 100644 --- a/src/librustdoc/passes/html_tags.rs +++ b/src/librustdoc/passes/html_tags.rs @@ -220,7 +220,7 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { // and we don't try to detect stuff `` because that's not valid Rust. if let Some(Some(generics_start)) = (is_open_tag && dox[..range.end].ends_with(">")) - .then(|| extract_path_backwards(&dox, range.start)) + .then(|| extract_path_backwards(&dox, range.start)) { let generics_sp = match super::source_span_for_markdown_range( tcx, @@ -231,22 +231,15 @@ impl<'a, 'tcx> DocVisitor for InvalidHtmlTagsLinter<'a, 'tcx> { Some(sp) => sp, None => item.attr_span(tcx), }; - if let Ok(generics_snippet) = - tcx.sess.source_map().span_to_snippet(generics_sp) - { - // short form is chosen here because ``Vec`` would be confusing. - diag.span_suggestion_short( - generics_sp, - "try marking as source code with `backticks`", - format!("`{}`", generics_snippet), - Applicability::MaybeIncorrect, - ); - } else { - diag.span_help( - generics_sp, - "try marking as source code with `backticks`", - ); - } + // multipart form is chosen here because ``Vec`` would be confusing. + diag.multipart_suggestion( + "try marking as source code", + vec![ + (generics_sp.shrink_to_lo(), String::from("`")), + (generics_sp.shrink_to_hi(), String::from("`")), + ], + Applicability::MaybeIncorrect, + ); } diag.emit() }); diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.fixed b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed index 04bdd038993a4..c0a0de24c5263 100644 --- a/src/test/rustdoc-ui/suggestions/html-as-generics.fixed +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.fixed @@ -4,35 +4,29 @@ /// This `Vec` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec` pub struct Generic; /// This `vec::Vec` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `vec::Vec` pub struct GenericPath; /// This `i32` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `i32` pub struct PathsCanContainTrailingNumbers; /// This `Vec::` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec::` pub struct Turbofish; /// This [link](https://rust-lang.org)`::` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `::` pub struct BareTurbofish; /// This `Vec::` thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec::` pub struct Nested; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.rs b/src/test/rustdoc-ui/suggestions/html-as-generics.rs index 28e50c0073851..0b6009b0e59c3 100644 --- a/src/test/rustdoc-ui/suggestions/html-as-generics.rs +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.rs @@ -4,35 +4,29 @@ /// This Vec thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec` pub struct Generic; /// This vec::Vec thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `vec::Vec` pub struct GenericPath; /// This i32 thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `i32` pub struct PathsCanContainTrailingNumbers; /// This Vec:: thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec::` pub struct Turbofish; /// This [link](https://rust-lang.org):: thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `::` pub struct BareTurbofish; /// This Vec:: thing! //~^ERROR unclosed HTML tag `i32` //~|HELP try marking as source -//~|SUGGESTION `Vec::` pub struct Nested; diff --git a/src/test/rustdoc-ui/suggestions/html-as-generics.stderr b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr index c0a1603bc6652..df54b71264ebc 100644 --- a/src/test/rustdoc-ui/suggestions/html-as-generics.stderr +++ b/src/test/rustdoc-ui/suggestions/html-as-generics.stderr @@ -2,55 +2,72 @@ error: unclosed HTML tag `i32` --> $DIR/html-as-generics.rs:4:13 | LL | /// This Vec thing! - | ---^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ | note: the lint level is defined here --> $DIR/html-as-generics.rs:2:9 | LL | #![deny(rustdoc::invalid_html_tags)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: try marking as source code + | +LL | /// This `Vec` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:10:18 + --> $DIR/html-as-generics.rs:9:18 | LL | /// This vec::Vec thing! - | --------^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `vec::Vec` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:16:13 + --> $DIR/html-as-generics.rs:14:13 | LL | /// This i32 thing! - | ---^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `i32` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:22:15 + --> $DIR/html-as-generics.rs:19:15 | LL | /// This Vec:: thing! - | -----^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `Vec::` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:28:41 + --> $DIR/html-as-generics.rs:24:41 | LL | /// This [link](https://rust-lang.org):: thing! - | --^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This [link](https://rust-lang.org)`::` thing! + | + + error: unclosed HTML tag `i32` - --> $DIR/html-as-generics.rs:34:21 + --> $DIR/html-as-generics.rs:29:21 | LL | /// This Vec:: thing! - | -----^^^^^ - | | - | help: try marking as source code with `backticks` + | ^^^^^ + | +help: try marking as source code + | +LL | /// This `Vec::` thing! + | + + error: aborting due to 6 previous errors