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;