summaryrefslogtreecommitdiffstats
path: root/src/librustdoc/html/markdown.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/librustdoc/html/markdown.rs')
-rw-r--r--src/librustdoc/html/markdown.rs665
1 files changed, 544 insertions, 121 deletions
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index 98cc38a10..d24e6e5fa 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -20,12 +20,14 @@
//! edition: Edition::Edition2015,
//! playground: &None,
//! heading_offset: HeadingOffset::H2,
+//! custom_code_classes_in_docs: true,
//! };
//! let html = md.into_string();
//! // ... something using html
//! ```
use rustc_data_structures::fx::FxHashMap;
+use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage};
use rustc_hir::def_id::DefId;
use rustc_middle::ty::TyCtxt;
pub(crate) use rustc_resolve::rustdoc::main_body_opts;
@@ -37,8 +39,9 @@ use once_cell::sync::Lazy;
use std::borrow::Cow;
use std::collections::VecDeque;
use std::fmt::Write;
+use std::iter::Peekable;
use std::ops::{ControlFlow, Range};
-use std::str;
+use std::str::{self, CharIndices};
use crate::clean::RenderedLink;
use crate::doctest;
@@ -93,6 +96,8 @@ pub struct Markdown<'a> {
/// Offset at which we render headings.
/// E.g. if `heading_offset: HeadingOffset::H2`, then `# something` renders an `<h2>`.
pub heading_offset: HeadingOffset,
+ /// `true` if the `custom_code_classes_in_docs` feature is enabled.
+ pub custom_code_classes_in_docs: bool,
}
/// A struct like `Markdown` that renders the markdown with a table of contents.
pub(crate) struct MarkdownWithToc<'a> {
@@ -101,6 +106,8 @@ pub(crate) struct MarkdownWithToc<'a> {
pub(crate) error_codes: ErrorCodes,
pub(crate) edition: Edition,
pub(crate) playground: &'a Option<Playground>,
+ /// `true` if the `custom_code_classes_in_docs` feature is enabled.
+ pub(crate) custom_code_classes_in_docs: bool,
}
/// A tuple struct like `Markdown` that renders the markdown escaping HTML tags
/// and includes no paragraph tags.
@@ -201,6 +208,7 @@ struct CodeBlocks<'p, 'a, I: Iterator<Item = Event<'a>>> {
// Information about the playground if a URL has been specified, containing an
// optional crate name and the URL.
playground: &'p Option<Playground>,
+ custom_code_classes_in_docs: bool,
}
impl<'p, 'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'p, 'a, I> {
@@ -209,8 +217,15 @@ impl<'p, 'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'p, 'a, I> {
error_codes: ErrorCodes,
edition: Edition,
playground: &'p Option<Playground>,
+ custom_code_classes_in_docs: bool,
) -> Self {
- CodeBlocks { inner: iter, check_error_codes: error_codes, edition, playground }
+ CodeBlocks {
+ inner: iter,
+ check_error_codes: error_codes,
+ edition,
+ playground,
+ custom_code_classes_in_docs,
+ }
}
}
@@ -240,14 +255,28 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
let parse_result = match kind {
CodeBlockKind::Fenced(ref lang) => {
- let parse_result =
- LangString::parse_without_check(lang, self.check_error_codes, false);
+ let parse_result = LangString::parse_without_check(
+ lang,
+ self.check_error_codes,
+ false,
+ self.custom_code_classes_in_docs,
+ );
if !parse_result.rust {
+ let added_classes = parse_result.added_classes;
+ let lang_string = if let Some(lang) = parse_result.unknown.first() {
+ format!("language-{}", lang)
+ } else {
+ String::new()
+ };
+ let whitespace = if added_classes.is_empty() { "" } else { " " };
return Some(Event::Html(
format!(
"<div class=\"example-wrap\">\
- <pre class=\"language-{lang}\"><code>{text}</code></pre>\
+ <pre class=\"{lang_string}{whitespace}{added_classes}\">\
+ <code>{text}</code>\
+ </pre>\
</div>",
+ added_classes = added_classes.join(" "),
text = Escape(&original_text),
)
.into(),
@@ -258,6 +287,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
CodeBlockKind::Indented => Default::default(),
};
+ let added_classes = parse_result.added_classes;
let lines = original_text.lines().filter_map(|l| map_line(l).for_html());
let text = lines.intersperse("\n".into()).collect::<String>();
@@ -315,6 +345,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
&mut s,
tooltip,
playground_button.as_deref(),
+ &added_classes,
);
Some(Event::Html(s.into_inner().into()))
}
@@ -711,6 +742,27 @@ pub(crate) fn find_testable_code<T: doctest::Tester>(
error_codes: ErrorCodes,
enable_per_target_ignores: bool,
extra_info: Option<&ExtraInfo<'_>>,
+ custom_code_classes_in_docs: bool,
+) {
+ find_codes(
+ doc,
+ tests,
+ error_codes,
+ enable_per_target_ignores,
+ extra_info,
+ false,
+ custom_code_classes_in_docs,
+ )
+}
+
+pub(crate) fn find_codes<T: doctest::Tester>(
+ doc: &str,
+ tests: &mut T,
+ error_codes: ErrorCodes,
+ enable_per_target_ignores: bool,
+ extra_info: Option<&ExtraInfo<'_>>,
+ include_non_rust: bool,
+ custom_code_classes_in_docs: bool,
) {
let mut parser = Parser::new(doc).into_offset_iter();
let mut prev_offset = 0;
@@ -729,12 +781,13 @@ pub(crate) fn find_testable_code<T: doctest::Tester>(
error_codes,
enable_per_target_ignores,
extra_info,
+ custom_code_classes_in_docs,
)
}
}
CodeBlockKind::Indented => Default::default(),
};
- if !block_info.rust {
+ if !include_non_rust && !block_info.rust {
continue;
}
@@ -784,7 +837,23 @@ impl<'tcx> ExtraInfo<'tcx> {
ExtraInfo { def_id, sp, tcx }
}
- fn error_invalid_codeblock_attr(&self, msg: String, help: &'static str) {
+ fn error_invalid_codeblock_attr(&self, msg: impl Into<DiagnosticMessage>) {
+ if let Some(def_id) = self.def_id.as_local() {
+ self.tcx.struct_span_lint_hir(
+ crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
+ self.tcx.hir().local_def_id_to_hir_id(def_id),
+ self.sp,
+ msg,
+ |l| l,
+ );
+ }
+ }
+
+ fn error_invalid_codeblock_attr_with_help(
+ &self,
+ msg: impl Into<DiagnosticMessage>,
+ help: impl Into<SubdiagnosticMessage>,
+ ) {
if let Some(def_id) = self.def_id.as_local() {
self.tcx.struct_span_lint_hir(
crate::lint::INVALID_CODEBLOCK_ATTRIBUTES,
@@ -799,7 +868,7 @@ impl<'tcx> ExtraInfo<'tcx> {
#[derive(Eq, PartialEq, Clone, Debug)]
pub(crate) struct LangString {
- original: String,
+ pub(crate) original: String,
pub(crate) should_panic: bool,
pub(crate) no_run: bool,
pub(crate) ignore: Ignore,
@@ -808,6 +877,8 @@ pub(crate) struct LangString {
pub(crate) compile_fail: bool,
pub(crate) error_codes: Vec<String>,
pub(crate) edition: Option<Edition>,
+ pub(crate) added_classes: Vec<String>,
+ pub(crate) unknown: Vec<String>,
}
#[derive(Eq, PartialEq, Clone, Debug)]
@@ -817,6 +888,317 @@ pub(crate) enum Ignore {
Some(Vec<String>),
}
+/// This is the parser for fenced codeblocks attributes. It implements the following eBNF:
+///
+/// ```eBNF
+/// lang-string = *(token-list / delimited-attribute-list / comment)
+///
+/// bareword = LEADINGCHAR *(CHAR)
+/// bareword-without-leading-char = CHAR *(CHAR)
+/// quoted-string = QUOTE *(NONQUOTE) QUOTE
+/// token = bareword / quoted-string
+/// token-without-leading-char = bareword-without-leading-char / quoted-string
+/// sep = COMMA/WS *(COMMA/WS)
+/// attribute = (DOT token)/(token EQUAL token-without-leading-char)
+/// attribute-list = [sep] attribute *(sep attribute) [sep]
+/// delimited-attribute-list = OPEN-CURLY-BRACKET attribute-list CLOSE-CURLY-BRACKET
+/// token-list = [sep] token *(sep token) [sep]
+/// comment = OPEN_PAREN *(all characters) CLOSE_PAREN
+///
+/// OPEN_PAREN = "("
+/// CLOSE_PARENT = ")"
+/// OPEN-CURLY-BRACKET = "{"
+/// CLOSE-CURLY-BRACKET = "}"
+/// LEADINGCHAR = ALPHA | DIGIT | "_" | "-" | ":"
+/// ; All ASCII punctuation except comma, quote, equals, backslash, grave (backquote) and braces.
+/// ; Comma is used to separate language tokens, so it can't be used in one.
+/// ; Quote is used to allow otherwise-disallowed characters in language tokens.
+/// ; Equals is used to make key=value pairs in attribute blocks.
+/// ; Backslash and grave are special Markdown characters.
+/// ; Braces are used to start an attribute block.
+/// CHAR = ALPHA | DIGIT | "_" | "-" | ":" | "." | "!" | "#" | "$" | "%" | "&" | "*" | "+" | "/" |
+/// ";" | "<" | ">" | "?" | "@" | "^" | "|" | "~"
+/// NONQUOTE = %x09 / %x20 / %x21 / %x23-7E ; TAB / SPACE / all printable characters except `"`
+/// COMMA = ","
+/// DOT = "."
+/// EQUAL = "="
+///
+/// ALPHA = %x41-5A / %x61-7A ; A-Z / a-z
+/// DIGIT = %x30-39
+/// WS = %x09 / " "
+/// ```
+pub(crate) struct TagIterator<'a, 'tcx> {
+ inner: Peekable<CharIndices<'a>>,
+ data: &'a str,
+ is_in_attribute_block: bool,
+ extra: Option<&'a ExtraInfo<'tcx>>,
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub(crate) enum LangStringToken<'a> {
+ LangToken(&'a str),
+ ClassAttribute(&'a str),
+ KeyValueAttribute(&'a str, &'a str),
+}
+
+fn is_leading_char(c: char) -> bool {
+ c == '_' || c == '-' || c == ':' || c.is_ascii_alphabetic() || c.is_ascii_digit()
+}
+fn is_bareword_char(c: char) -> bool {
+ is_leading_char(c) || ".!#$%&*+/;<>?@^|~".contains(c)
+}
+fn is_separator(c: char) -> bool {
+ c == ' ' || c == ',' || c == '\t'
+}
+
+struct Indices {
+ start: usize,
+ end: usize,
+}
+
+impl<'a, 'tcx> TagIterator<'a, 'tcx> {
+ pub(crate) fn new(data: &'a str, extra: Option<&'a ExtraInfo<'tcx>>) -> Self {
+ Self { inner: data.char_indices().peekable(), data, is_in_attribute_block: false, extra }
+ }
+
+ fn emit_error(&self, err: impl Into<DiagnosticMessage>) {
+ if let Some(extra) = self.extra {
+ extra.error_invalid_codeblock_attr(err);
+ }
+ }
+
+ fn skip_separators(&mut self) -> Option<usize> {
+ while let Some((pos, c)) = self.inner.peek() {
+ if !is_separator(*c) {
+ return Some(*pos);
+ }
+ self.inner.next();
+ }
+ None
+ }
+
+ fn parse_string(&mut self, start: usize) -> Option<Indices> {
+ while let Some((pos, c)) = self.inner.next() {
+ if c == '"' {
+ return Some(Indices { start: start + 1, end: pos });
+ }
+ }
+ self.emit_error("unclosed quote string `\"`");
+ None
+ }
+
+ fn parse_class(&mut self, start: usize) -> Option<LangStringToken<'a>> {
+ while let Some((pos, c)) = self.inner.peek().copied() {
+ if is_bareword_char(c) {
+ self.inner.next();
+ } else {
+ let class = &self.data[start + 1..pos];
+ if class.is_empty() {
+ self.emit_error(format!("unexpected `{c}` character after `.`"));
+ return None;
+ } else if self.check_after_token() {
+ return Some(LangStringToken::ClassAttribute(class));
+ } else {
+ return None;
+ }
+ }
+ }
+ let class = &self.data[start + 1..];
+ if class.is_empty() {
+ self.emit_error("missing character after `.`");
+ None
+ } else if self.check_after_token() {
+ Some(LangStringToken::ClassAttribute(class))
+ } else {
+ None
+ }
+ }
+
+ fn parse_token(&mut self, start: usize) -> Option<Indices> {
+ while let Some((pos, c)) = self.inner.peek() {
+ if !is_bareword_char(*c) {
+ return Some(Indices { start, end: *pos });
+ }
+ self.inner.next();
+ }
+ self.emit_error("unexpected end");
+ None
+ }
+
+ fn parse_key_value(&mut self, c: char, start: usize) -> Option<LangStringToken<'a>> {
+ let key_indices =
+ if c == '"' { self.parse_string(start)? } else { self.parse_token(start)? };
+ if key_indices.start == key_indices.end {
+ self.emit_error("unexpected empty string as key");
+ return None;
+ }
+
+ if let Some((_, c)) = self.inner.next() {
+ if c != '=' {
+ self.emit_error(format!("expected `=`, found `{}`", c));
+ return None;
+ }
+ } else {
+ self.emit_error("unexpected end");
+ return None;
+ }
+ let value_indices = match self.inner.next() {
+ Some((pos, '"')) => self.parse_string(pos)?,
+ Some((pos, c)) if is_bareword_char(c) => self.parse_token(pos)?,
+ Some((_, c)) => {
+ self.emit_error(format!("unexpected `{c}` character after `=`"));
+ return None;
+ }
+ None => {
+ self.emit_error("expected value after `=`");
+ return None;
+ }
+ };
+ if value_indices.start == value_indices.end {
+ self.emit_error("unexpected empty string as value");
+ None
+ } else if self.check_after_token() {
+ Some(LangStringToken::KeyValueAttribute(
+ &self.data[key_indices.start..key_indices.end],
+ &self.data[value_indices.start..value_indices.end],
+ ))
+ } else {
+ None
+ }
+ }
+
+ /// Returns `false` if an error was emitted.
+ fn check_after_token(&mut self) -> bool {
+ if let Some((_, c)) = self.inner.peek().copied() {
+ if c == '}' || is_separator(c) || c == '(' {
+ true
+ } else {
+ self.emit_error(format!("unexpected `{c}` character"));
+ false
+ }
+ } else {
+ // The error will be caught on the next iteration.
+ true
+ }
+ }
+
+ fn parse_in_attribute_block(&mut self) -> Option<LangStringToken<'a>> {
+ if let Some((pos, c)) = self.inner.next() {
+ if c == '}' {
+ self.is_in_attribute_block = false;
+ return self.next();
+ } else if c == '.' {
+ return self.parse_class(pos);
+ } else if c == '"' || is_leading_char(c) {
+ return self.parse_key_value(c, pos);
+ } else {
+ self.emit_error(format!("unexpected character `{c}`"));
+ return None;
+ }
+ }
+ self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
+ None
+ }
+
+ /// Returns `false` if an error was emitted.
+ fn skip_paren_block(&mut self) -> bool {
+ while let Some((_, c)) = self.inner.next() {
+ if c == ')' {
+ return true;
+ }
+ }
+ self.emit_error("unclosed comment: missing `)` at the end");
+ false
+ }
+
+ fn parse_outside_attribute_block(&mut self, start: usize) -> Option<LangStringToken<'a>> {
+ while let Some((pos, c)) = self.inner.next() {
+ if c == '"' {
+ if pos != start {
+ self.emit_error("expected ` `, `{` or `,` found `\"`");
+ return None;
+ }
+ let indices = self.parse_string(pos)?;
+ if let Some((_, c)) = self.inner.peek().copied() &&
+ c != '{' &&
+ !is_separator(c) &&
+ c != '('
+ {
+ self.emit_error(format!("expected ` `, `{{` or `,` after `\"`, found `{c}`"));
+ return None;
+ }
+ return Some(LangStringToken::LangToken(&self.data[indices.start..indices.end]));
+ } else if c == '{' {
+ self.is_in_attribute_block = true;
+ return self.next();
+ } else if is_separator(c) {
+ if pos != start {
+ return Some(LangStringToken::LangToken(&self.data[start..pos]));
+ }
+ return self.next();
+ } else if c == '(' {
+ if !self.skip_paren_block() {
+ return None;
+ }
+ if pos != start {
+ return Some(LangStringToken::LangToken(&self.data[start..pos]));
+ }
+ return self.next();
+ } else if pos == start && is_leading_char(c) {
+ continue;
+ } else if pos != start && is_bareword_char(c) {
+ continue;
+ } else {
+ self.emit_error(format!("unexpected character `{c}`"));
+ return None;
+ }
+ }
+ let token = &self.data[start..];
+ if token.is_empty() { None } else { Some(LangStringToken::LangToken(&self.data[start..])) }
+ }
+}
+
+impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
+ type Item = LangStringToken<'a>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ let Some(start) = self.skip_separators() else {
+ if self.is_in_attribute_block {
+ self.emit_error("unclosed attribute block (`{}`): missing `}` at the end");
+ }
+ return None;
+ };
+ if self.is_in_attribute_block {
+ self.parse_in_attribute_block()
+ } else {
+ self.parse_outside_attribute_block(start)
+ }
+ }
+}
+
+fn tokens(string: &str) -> impl Iterator<Item = LangStringToken<'_>> {
+ // Pandoc, which Rust once used for generating documentation,
+ // expects lang strings to be surrounded by `{}` and for each token
+ // to be proceeded by a `.`. Since some of these lang strings are still
+ // loose in the wild, we strip a pair of surrounding `{}` from the lang
+ // string and a leading `.` from each token.
+
+ let string = string.trim();
+
+ let first = string.chars().next();
+ let last = string.chars().last();
+
+ let string =
+ if first == Some('{') && last == Some('}') { &string[1..string.len() - 1] } else { string };
+
+ string
+ .split(|c| c == ',' || c == ' ' || c == '\t')
+ .map(str::trim)
+ .map(|token| token.strip_prefix('.').unwrap_or(token))
+ .filter(|token| !token.is_empty())
+ .map(|token| LangStringToken::LangToken(token))
+}
+
impl Default for LangString {
fn default() -> Self {
Self {
@@ -829,6 +1211,8 @@ impl Default for LangString {
compile_fail: false,
error_codes: Vec::new(),
edition: None,
+ added_classes: Vec::new(),
+ unknown: Vec::new(),
}
}
}
@@ -838,33 +1222,15 @@ impl LangString {
string: &str,
allow_error_code_check: ErrorCodes,
enable_per_target_ignores: bool,
- ) -> LangString {
- Self::parse(string, allow_error_code_check, enable_per_target_ignores, None)
- }
-
- fn tokens(string: &str) -> impl Iterator<Item = &str> {
- // Pandoc, which Rust once used for generating documentation,
- // expects lang strings to be surrounded by `{}` and for each token
- // to be proceeded by a `.`. Since some of these lang strings are still
- // loose in the wild, we strip a pair of surrounding `{}` from the lang
- // string and a leading `.` from each token.
-
- let string = string.trim();
-
- let first = string.chars().next();
- let last = string.chars().last();
-
- let string = if first == Some('{') && last == Some('}') {
- &string[1..string.len() - 1]
- } else {
- string
- };
-
- string
- .split(|c| c == ',' || c == ' ' || c == '\t')
- .map(str::trim)
- .map(|token| token.strip_prefix('.').unwrap_or(token))
- .filter(|token| !token.is_empty())
+ custom_code_classes_in_docs: bool,
+ ) -> Self {
+ Self::parse(
+ string,
+ allow_error_code_check,
+ enable_per_target_ignores,
+ None,
+ custom_code_classes_in_docs,
+ )
}
fn parse(
@@ -872,102 +1238,141 @@ impl LangString {
allow_error_code_check: ErrorCodes,
enable_per_target_ignores: bool,
extra: Option<&ExtraInfo<'_>>,
- ) -> LangString {
+ custom_code_classes_in_docs: bool,
+ ) -> Self {
let allow_error_code_check = allow_error_code_check.as_bool();
let mut seen_rust_tags = false;
let mut seen_other_tags = false;
+ let mut seen_custom_tag = false;
let mut data = LangString::default();
let mut ignores = vec![];
data.original = string.to_owned();
- for token in Self::tokens(string) {
- match token {
- "should_panic" => {
- data.should_panic = true;
- seen_rust_tags = !seen_other_tags;
- }
- "no_run" => {
- data.no_run = true;
- seen_rust_tags = !seen_other_tags;
- }
- "ignore" => {
- data.ignore = Ignore::All;
- seen_rust_tags = !seen_other_tags;
- }
- x if x.starts_with("ignore-") => {
- if enable_per_target_ignores {
- ignores.push(x.trim_start_matches("ignore-").to_owned());
+ let mut call = |tokens: &mut dyn Iterator<Item = LangStringToken<'_>>| {
+ for token in tokens {
+ match token {
+ LangStringToken::LangToken("should_panic") => {
+ data.should_panic = true;
seen_rust_tags = !seen_other_tags;
}
- }
- "rust" => {
- data.rust = true;
- seen_rust_tags = true;
- }
- "test_harness" => {
- data.test_harness = true;
- seen_rust_tags = !seen_other_tags || seen_rust_tags;
- }
- "compile_fail" => {
- data.compile_fail = true;
- seen_rust_tags = !seen_other_tags || seen_rust_tags;
- data.no_run = true;
- }
- x if x.starts_with("edition") => {
- data.edition = x[7..].parse::<Edition>().ok();
- }
- x if allow_error_code_check && x.starts_with('E') && x.len() == 5 => {
- if x[1..].parse::<u32>().is_ok() {
- data.error_codes.push(x.to_owned());
+ LangStringToken::LangToken("no_run") => {
+ data.no_run = true;
+ seen_rust_tags = !seen_other_tags;
+ }
+ LangStringToken::LangToken("ignore") => {
+ data.ignore = Ignore::All;
+ seen_rust_tags = !seen_other_tags;
+ }
+ LangStringToken::LangToken(x) if x.starts_with("ignore-") => {
+ if enable_per_target_ignores {
+ ignores.push(x.trim_start_matches("ignore-").to_owned());
+ seen_rust_tags = !seen_other_tags;
+ }
+ }
+ LangStringToken::LangToken("rust") => {
+ data.rust = true;
+ seen_rust_tags = true;
+ }
+ LangStringToken::LangToken("custom") => {
+ if custom_code_classes_in_docs {
+ seen_custom_tag = true;
+ } else {
+ seen_other_tags = true;
+ }
+ }
+ LangStringToken::LangToken("test_harness") => {
+ data.test_harness = true;
seen_rust_tags = !seen_other_tags || seen_rust_tags;
- } else {
- seen_other_tags = true;
}
- }
- x if extra.is_some() => {
- let s = x.to_lowercase();
- if let Some((flag, help)) = if s == "compile-fail"
- || s == "compile_fail"
- || s == "compilefail"
+ LangStringToken::LangToken("compile_fail") => {
+ data.compile_fail = true;
+ seen_rust_tags = !seen_other_tags || seen_rust_tags;
+ data.no_run = true;
+ }
+ LangStringToken::LangToken(x) if x.starts_with("edition") => {
+ data.edition = x[7..].parse::<Edition>().ok();
+ }
+ LangStringToken::LangToken(x)
+ if allow_error_code_check && x.starts_with('E') && x.len() == 5 =>
{
- Some((
- "compile_fail",
- "the code block will either not be tested if not marked as a rust one \
- or won't fail if it compiles successfully",
- ))
- } else if s == "should-panic" || s == "should_panic" || s == "shouldpanic" {
- Some((
- "should_panic",
- "the code block will either not be tested if not marked as a rust one \
- or won't fail if it doesn't panic when running",
- ))
- } else if s == "no-run" || s == "no_run" || s == "norun" {
- Some((
- "no_run",
- "the code block will either not be tested if not marked as a rust one \
- or will be run (which you might not want)",
- ))
- } else if s == "test-harness" || s == "test_harness" || s == "testharness" {
- Some((
- "test_harness",
- "the code block will either not be tested if not marked as a rust one \
- or the code will be wrapped inside a main function",
- ))
- } else {
- None
- } {
- if let Some(extra) = extra {
- extra.error_invalid_codeblock_attr(
- format!("unknown attribute `{x}`. Did you mean `{flag}`?"),
- help,
- );
+ if x[1..].parse::<u32>().is_ok() {
+ data.error_codes.push(x.to_owned());
+ seen_rust_tags = !seen_other_tags || seen_rust_tags;
+ } else {
+ seen_other_tags = true;
}
}
- seen_other_tags = true;
+ LangStringToken::LangToken(x) if extra.is_some() => {
+ let s = x.to_lowercase();
+ if let Some((flag, help)) = if s == "compile-fail"
+ || s == "compile_fail"
+ || s == "compilefail"
+ {
+ Some((
+ "compile_fail",
+ "the code block will either not be tested if not marked as a rust one \
+ or won't fail if it compiles successfully",
+ ))
+ } else if s == "should-panic" || s == "should_panic" || s == "shouldpanic" {
+ Some((
+ "should_panic",
+ "the code block will either not be tested if not marked as a rust one \
+ or won't fail if it doesn't panic when running",
+ ))
+ } else if s == "no-run" || s == "no_run" || s == "norun" {
+ Some((
+ "no_run",
+ "the code block will either not be tested if not marked as a rust one \
+ or will be run (which you might not want)",
+ ))
+ } else if s == "test-harness" || s == "test_harness" || s == "testharness" {
+ Some((
+ "test_harness",
+ "the code block will either not be tested if not marked as a rust one \
+ or the code will be wrapped inside a main function",
+ ))
+ } else {
+ None
+ } {
+ if let Some(extra) = extra {
+ extra.error_invalid_codeblock_attr_with_help(
+ format!("unknown attribute `{x}`. Did you mean `{flag}`?"),
+ help,
+ );
+ }
+ }
+ seen_other_tags = true;
+ data.unknown.push(x.to_owned());
+ }
+ LangStringToken::LangToken(x) => {
+ seen_other_tags = true;
+ data.unknown.push(x.to_owned());
+ }
+ LangStringToken::KeyValueAttribute(key, value) => {
+ if custom_code_classes_in_docs {
+ if key == "class" {
+ data.added_classes.push(value.to_owned());
+ } else if let Some(extra) = extra {
+ extra.error_invalid_codeblock_attr(format!(
+ "unsupported attribute `{key}`"
+ ));
+ }
+ } else {
+ seen_other_tags = true;
+ }
+ }
+ LangStringToken::ClassAttribute(class) => {
+ data.added_classes.push(class.to_owned());
+ }
}
- _ => seen_other_tags = true,
}
+ };
+
+ if custom_code_classes_in_docs {
+ call(&mut TagIterator::new(string, extra).into_iter())
+ } else {
+ call(&mut tokens(string))
}
// ignore-foo overrides ignore
@@ -975,7 +1380,7 @@ impl LangString {
data.ignore = Ignore::Some(ignores);
}
- data.rust &= !seen_other_tags || seen_rust_tags;
+ data.rust &= !seen_custom_tag && (!seen_other_tags || seen_rust_tags);
data
}
@@ -991,6 +1396,7 @@ impl Markdown<'_> {
edition,
playground,
heading_offset,
+ custom_code_classes_in_docs,
} = self;
// This is actually common enough to special-case
@@ -1013,7 +1419,7 @@ impl Markdown<'_> {
let p = Footnotes::new(p);
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
let p = TableWrapper::new(p);
- let p = CodeBlocks::new(p, codes, edition, playground);
+ let p = CodeBlocks::new(p, codes, edition, playground, custom_code_classes_in_docs);
html::push_html(&mut s, p);
s
@@ -1022,7 +1428,14 @@ impl Markdown<'_> {
impl MarkdownWithToc<'_> {
pub(crate) fn into_string(self) -> String {
- let MarkdownWithToc { content: md, ids, error_codes: codes, edition, playground } = self;
+ let MarkdownWithToc {
+ content: md,
+ ids,
+ error_codes: codes,
+ edition,
+ playground,
+ custom_code_classes_in_docs,
+ } = self;
let p = Parser::new_ext(md, main_body_opts()).into_offset_iter();
@@ -1034,7 +1447,7 @@ impl MarkdownWithToc<'_> {
let p = HeadingLinks::new(p, Some(&mut toc), ids, HeadingOffset::H1);
let p = Footnotes::new(p);
let p = TableWrapper::new(p.map(|(ev, _)| ev));
- let p = CodeBlocks::new(p, codes, edition, playground);
+ let p = CodeBlocks::new(p, codes, edition, playground, custom_code_classes_in_docs);
html::push_html(&mut s, p);
}
@@ -1475,7 +1888,11 @@ pub(crate) struct RustCodeBlock {
/// Returns a range of bytes for each code block in the markdown that is tagged as `rust` or
/// untagged (and assumed to be rust).
-pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<RustCodeBlock> {
+pub(crate) fn rust_code_blocks(
+ md: &str,
+ extra_info: &ExtraInfo<'_>,
+ custom_code_classes_in_docs: bool,
+) -> Vec<RustCodeBlock> {
let mut code_blocks = vec![];
if md.is_empty() {
@@ -1492,7 +1909,13 @@ pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<Rust
let lang_string = if syntax.is_empty() {
Default::default()
} else {
- LangString::parse(&*syntax, ErrorCodes::Yes, false, Some(extra_info))
+ LangString::parse(
+ &*syntax,
+ ErrorCodes::Yes,
+ false,
+ Some(extra_info),
+ custom_code_classes_in_docs,
+ )
};
if !lang_string.rust {
continue;
@@ -1574,7 +1997,6 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
map.insert("crate-search-div".into(), 1);
// This is the list of IDs used in HTML generated in Rust (including the ones
// used in tera template files).
- map.insert("mainThemeStyle".into(), 1);
map.insert("themeStyle".into(), 1);
map.insert("settings-menu".into(), 1);
map.insert("help-button".into(), 1);
@@ -1610,6 +2032,7 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> {
map.insert("blanket-implementations-list".into(), 1);
map.insert("deref-methods".into(), 1);
map.insert("layout".into(), 1);
+ map.insert("aliased-type".into(), 1);
map
}