diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs
index 2c9c95590ac..dd3d0ebac0c 100644
--- a/src/librustdoc/html/markdown/tests.rs
+++ b/src/librustdoc/html/markdown/tests.rs
@@ -1,5 +1,8 @@
use super::{find_testable_code, plain_text_summary, short_markdown_summary};
-use super::{ErrorCodes, HeadingOffset, IdMap, Ignore, LangString, Markdown, MarkdownItemInfo};
+use super::{
+ ErrorCodes, HeadingOffset, IdMap, Ignore, LangString, Markdown, MarkdownItemInfo, TagIterator,
+ TokenKind,
+};
use rustc_span::edition::{Edition, DEFAULT_EDITION};
#[test]
@@ -51,10 +54,25 @@ fn test_lang_string_parse() {
t(Default::default());
t(LangString { original: "rust".into(), ..Default::default() });
- t(LangString { original: ".rust".into(), ..Default::default() });
- t(LangString { original: "{rust}".into(), ..Default::default() });
- t(LangString { original: "{.rust}".into(), ..Default::default() });
- t(LangString { original: "sh".into(), rust: false, ..Default::default() });
+ t(LangString {
+ original: ".rust".into(),
+ rust: false,
+ unknown: vec![".rust".into()],
+ ..Default::default()
+ });
+ t(LangString { original: "{rust}".into(), rust: false, ..Default::default() });
+ t(LangString {
+ original: "{.rust}".into(),
+ rust: false,
+ added_classes: vec!["rust".into()],
+ ..Default::default()
+ });
+ t(LangString {
+ original: "sh".into(),
+ rust: false,
+ unknown: vec!["sh".into()],
+ ..Default::default()
+ });
t(LangString { original: "ignore".into(), ignore: Ignore::All, ..Default::default() });
t(LangString {
original: "ignore-foo".into(),
@@ -70,41 +88,56 @@ fn test_lang_string_parse() {
compile_fail: true,
..Default::default()
});
- t(LangString { original: "no_run,example".into(), no_run: true, ..Default::default() });
+ t(LangString {
+ original: "no_run,example".into(),
+ no_run: true,
+ unknown: vec!["example".into()],
+ ..Default::default()
+ });
t(LangString {
original: "sh,should_panic".into(),
should_panic: true,
rust: false,
+ unknown: vec!["sh".into()],
+ ..Default::default()
+ });
+ t(LangString {
+ original: "example,rust".into(),
+ unknown: vec!["example".into()],
..Default::default()
});
- t(LangString { original: "example,rust".into(), ..Default::default() });
t(LangString {
original: "test_harness,.rust".into(),
test_harness: true,
+ unknown: vec![".rust".into()],
..Default::default()
});
t(LangString {
original: "text, no_run".into(),
no_run: true,
rust: false,
+ unknown: vec!["text".into()],
..Default::default()
});
t(LangString {
original: "text,no_run".into(),
no_run: true,
rust: false,
+ unknown: vec!["text".into()],
..Default::default()
});
t(LangString {
original: "text,no_run, ".into(),
no_run: true,
rust: false,
+ unknown: vec!["text".into()],
..Default::default()
});
t(LangString {
original: "text,no_run,".into(),
no_run: true,
rust: false,
+ unknown: vec!["text".into()],
..Default::default()
});
t(LangString {
@@ -118,52 +151,96 @@ fn test_lang_string_parse() {
..Default::default()
});
t(LangString {
- original: "class:test".into(),
+ original: "{class=test}".into(),
added_classes: vec!["test".into()],
rust: false,
..Default::default()
});
t(LangString {
- original: "rust,class:test".into(),
+ original: "{.test}".into(),
added_classes: vec!["test".into()],
+ rust: false,
+ ..Default::default()
+ });
+ t(LangString {
+ original: "rust,{class=test,.test2}".into(),
+ added_classes: vec!["test".into(), "test2".into()],
rust: true,
..Default::default()
});
t(LangString {
- original: "class:test:with:colon".into(),
- added_classes: vec!["test:with:colon".into()],
+ original: "{class=test:with:colon .test1}".into(),
+ added_classes: vec!["test:with:colon".into(), "test1".into()],
rust: false,
..Default::default()
});
t(LangString {
- original: "class:first,class:second".into(),
+ original: "{class=first,class=second}".into(),
added_classes: vec!["first".into(), "second".into()],
rust: false,
..Default::default()
});
+ t(LangString {
+ original: "{class=first,.second},unknown".into(),
+ added_classes: vec!["first".into(), "second".into()],
+ rust: false,
+ unknown: vec!["unknown".into()],
+ ..Default::default()
+ });
+ t(LangString {
+ original: "{class=first .second} unknown".into(),
+ added_classes: vec!["first".into(), "second".into()],
+ rust: false,
+ unknown: vec!["unknown".into()],
+ ..Default::default()
+ });
+ t(LangString {
+ original: "{.first.second}".into(),
+ added_classes: vec!["first.second".into()],
+ rust: false,
+ ..Default::default()
+ });
+ t(LangString {
+ original: "{class=first=second}".into(),
+ added_classes: vec!["first=second".into()],
+ rust: false,
+ ..Default::default()
+ });
+ t(LangString {
+ original: "{class=first.second}".into(),
+ added_classes: vec!["first.second".into()],
+ rust: false,
+ ..Default::default()
+ });
+ t(LangString {
+ original: "{class=.first}".into(),
+ added_classes: vec![".first".into()],
+ rust: false,
+ ..Default::default()
+ });
}
#[test]
fn test_lang_string_tokenizer() {
- fn case(lang_string: &str, want: &[&str]) {
- let have = LangString::tokens(lang_string).collect::>();
+ fn case(lang_string: &str, want: &[TokenKind<'_>]) {
+ let have = TagIterator::new(lang_string, None).collect::>();
assert_eq!(have, want, "Unexpected lang string split for `{}`", lang_string);
}
case("", &[]);
- case("foo", &["foo"]);
- case("foo,bar", &["foo", "bar"]);
- case(".foo,.bar", &["foo", "bar"]);
- case("{.foo,.bar}", &["foo", "bar"]);
- case(" {.foo,.bar} ", &["foo", "bar"]);
- case("foo bar", &["foo", "bar"]);
- case("foo\tbar", &["foo", "bar"]);
- case("foo\t, bar", &["foo", "bar"]);
- case(" foo , bar ", &["foo", "bar"]);
- case(",,foo,,bar,,", &["foo", "bar"]);
- case("foo=bar", &["foo=bar"]);
- case("a-b-c", &["a-b-c"]);
- case("a_b_c", &["a_b_c"]);
+ case("foo", &[TokenKind::Token("foo")]);
+ case("foo,bar", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
+ case(".foo,.bar", &[TokenKind::Token(".foo"), TokenKind::Token(".bar")]);
+ case("{.foo,.bar}", &[TokenKind::Attribute(".foo"), TokenKind::Attribute(".bar")]);
+ case(" {.foo,.bar} ", &[TokenKind::Attribute(".foo"), TokenKind::Attribute(".bar")]);
+ case("foo bar", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
+ case("foo\tbar", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
+ case("foo\t, bar", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
+ case(" foo , bar ", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
+ case(",,foo,,bar,,", &[TokenKind::Token("foo"), TokenKind::Token("bar")]);
+ case("foo=bar", &[TokenKind::Token("foo=bar")]);
+ case("a-b-c", &[TokenKind::Token("a-b-c")]);
+ case("a_b_c", &[TokenKind::Token("a_b_c")]);
}
#[test]
diff --git a/tests/rustdoc-ui/custom_code_classes_in_docs-warning.rs b/tests/rustdoc-ui/custom_code_classes_in_docs-warning.rs
new file mode 100644
index 00000000000..c28921b01f1
--- /dev/null
+++ b/tests/rustdoc-ui/custom_code_classes_in_docs-warning.rs
@@ -0,0 +1,19 @@
+// This test ensures that warnings are working as expected for "custom_code_classes_in_docs"
+// feature.
+
+#![feature(custom_code_classes_in_docs)]
+#![deny(warnings)]
+#![feature(no_core)]
+#![no_core]
+
+/// ```{. class= whatever=hehe #id} } {{
+/// main;
+/// ```
+//~^^^ ERROR missing class name after `.`
+//~| ERROR missing class name after `class=`
+//~| ERROR unsupported attribute `whatever=hehe`
+//~| ERROR unsupported attribute `#id`
+//~| ERROR unexpected `}` outside attribute block (`{}`)
+//~| ERROR unclosed attribute block (`{}`): missing `}` at the end
+//~| ERROR unexpected `{` inside attribute block (`{}`)
+pub fn foo() {}
diff --git a/tests/rustdoc-ui/custom_code_classes_in_docs-warning.stderr b/tests/rustdoc-ui/custom_code_classes_in_docs-warning.stderr
new file mode 100644
index 00000000000..f19b62914db
--- /dev/null
+++ b/tests/rustdoc-ui/custom_code_classes_in_docs-warning.stderr
@@ -0,0 +1,65 @@
+error: missing class name after `.`
+ --> $DIR/custom_code_classes_in_docs-warning.rs:9:1
+ |
+LL | / /// ```{. class= whatever=hehe #id} } {{
+LL | | /// main;
+LL | | /// ```
+ | |_______^
+ |
+note: the lint level is defined here
+ --> $DIR/custom_code_classes_in_docs-warning.rs:5:9
+ |
+LL | #![deny(warnings)]
+ | ^^^^^^^^
+ = note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
+
+error: missing class name after `class=`
+ --> $DIR/custom_code_classes_in_docs-warning.rs:9:1
+ |
+LL | / /// ```{. class= whatever=hehe #id} } {{
+LL | | /// main;
+LL | | /// ```
+ | |_______^
+
+error: unsupported attribute `whatever=hehe`
+ --> $DIR/custom_code_classes_in_docs-warning.rs:9:1
+ |
+LL | / /// ```{. class= whatever=hehe #id} } {{
+LL | | /// main;
+LL | | /// ```
+ | |_______^
+
+error: unsupported attribute `#id`
+ --> $DIR/custom_code_classes_in_docs-warning.rs:9:1
+ |
+LL | / /// ```{. class= whatever=hehe #id} } {{
+LL | | /// main;
+LL | | /// ```
+ | |_______^
+
+error: unexpected `}` outside attribute block (`{}`)
+ --> $DIR/custom_code_classes_in_docs-warning.rs:9:1
+ |
+LL | / /// ```{. class= whatever=hehe #id} } {{
+LL | | /// main;
+LL | | /// ```
+ | |_______^
+
+error: unexpected `{` inside attribute block (`{}`)
+ --> $DIR/custom_code_classes_in_docs-warning.rs:9:1
+ |
+LL | / /// ```{. class= whatever=hehe #id} } {{
+LL | | /// main;
+LL | | /// ```
+ | |_______^
+
+error: unclosed attribute block (`{}`): missing `}` at the end
+ --> $DIR/custom_code_classes_in_docs-warning.rs:9:1
+ |
+LL | / /// ```{. class= whatever=hehe #id} } {{
+LL | | /// main;
+LL | | /// ```
+ | |_______^
+
+error: aborting due to 7 previous errors
+
diff --git a/tests/rustdoc-ui/custom_code_classes_in_docs-warning2.rs b/tests/rustdoc-ui/custom_code_classes_in_docs-warning2.rs
new file mode 100644
index 00000000000..b2ce7407ec6
--- /dev/null
+++ b/tests/rustdoc-ui/custom_code_classes_in_docs-warning2.rs
@@ -0,0 +1,13 @@
+// This test ensures that warnings are working as expected for "custom_code_classes_in_docs"
+// feature.
+
+#![feature(custom_code_classes_in_docs)]
+#![deny(warnings)]
+#![feature(no_core)]
+#![no_core]
+
+/// ```{class=}
+/// main;
+/// ```
+//~^^^ ERROR missing class name after `class=`
+pub fn foo() {}
diff --git a/tests/rustdoc-ui/custom_code_classes_in_docs-warning2.stderr b/tests/rustdoc-ui/custom_code_classes_in_docs-warning2.stderr
new file mode 100644
index 00000000000..52bb1dae9f6
--- /dev/null
+++ b/tests/rustdoc-ui/custom_code_classes_in_docs-warning2.stderr
@@ -0,0 +1,17 @@
+error: missing class name after `class=`
+ --> $DIR/custom_code_classes_in_docs-warning2.rs:9:1
+ |
+LL | / /// ```{class=}
+LL | | /// main;
+LL | | /// ```
+ | |_______^
+ |
+note: the lint level is defined here
+ --> $DIR/custom_code_classes_in_docs-warning2.rs:5:9
+ |
+LL | #![deny(warnings)]
+ | ^^^^^^^^
+ = note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
+
+error: aborting due to previous error
+
diff --git a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs
new file mode 100644
index 00000000000..8aa13b2d5d1
--- /dev/null
+++ b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs
@@ -0,0 +1,5 @@
+/// ```{class=language-c}
+/// int main(void) { return 0; }
+/// ```
+//~^^^ ERROR 1:1: 3:8: custom classes in code blocks are unstable [E0658]
+pub struct Bar;
diff --git a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr
new file mode 100644
index 00000000000..c41ebfc8073
--- /dev/null
+++ b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr
@@ -0,0 +1,15 @@
+error[E0658]: custom classes in code blocks are unstable
+ --> $DIR/feature-gate-custom_code_classes_in_docs.rs:1:1
+ |
+LL | / /// ```{class=language-c}
+LL | | /// int main(void) { return 0; }
+LL | | /// ```
+ | |_______^
+ |
+ = note: see issue #79483 for more information
+ = help: add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable
+ = note: found these custom classes: class=language-c
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/rustdoc-ui/issues/issue-91713.stdout b/tests/rustdoc-ui/issues/issue-91713.stdout
index 16783524363..bbea7e5c212 100644
--- a/tests/rustdoc-ui/issues/issue-91713.stdout
+++ b/tests/rustdoc-ui/issues/issue-91713.stdout
@@ -1,4 +1,5 @@
Available passes for running rustdoc:
+check-custom-code-classes - check for custom code classes without the feature-gate enabled
check_doc_test_visibility - run various visibility-related lints on doctests
strip-hidden - strips all `#[doc(hidden)]` items from the output
strip-private - strips all private items from a crate which cannot be seen externally, implies strip-priv-imports
@@ -10,6 +11,7 @@ calculate-doc-coverage - counts the number of items with and without documentati
run-lints - runs some of rustdoc's lints
Default passes for rustdoc:
+check-custom-code-classes
collect-trait-impls
check_doc_test_visibility
strip-hidden (when not --document-hidden-items)
diff --git a/tests/rustdoc/custom_code_classes.rs b/tests/rustdoc/custom_code_classes.rs
new file mode 100644
index 00000000000..f110721c5a7
--- /dev/null
+++ b/tests/rustdoc/custom_code_classes.rs
@@ -0,0 +1,28 @@
+// Test for `custom_code_classes_in_docs` feature.
+
+#![feature(custom_code_classes_in_docs)]
+#![crate_name = "foo"]
+#![feature(no_core)]
+#![no_core]
+
+// @has 'foo/struct.Bar.html'
+// @has - '//*[@id="main-content"]//pre[@class="language-whatever hoho-c"]' 'main;'
+// @has - '//*[@id="main-content"]//pre[@class="language-whatever2 haha-c"]' 'main;'
+// @has - '//*[@id="main-content"]//pre[@class="language-whatever4 huhu-c"]' 'main;'
+
+/// ```{class=hoho-c},whatever
+/// main;
+/// ```
+///
+/// Testing multiple kinds of orders.
+///
+/// ```whatever2 {class=haha-c}
+/// main;
+/// ```
+///
+/// Testing with multiple "unknown". Only the first should be used.
+///
+/// ```whatever4{.huhu-c} whatever5
+/// main;
+/// ```
+pub struct Bar;