Auto merge of #124577 - GuillaumeGomez:stabilize-custom_code_classes_in_docs, r=rustdoc

Stabilize `custom_code_classes_in_docs` feature

Fixes #79483.

This feature has been around for quite some time now, I think it's fine to stabilize it now.

## Summary

## What is the feature about?

In short, this PR changes two things, both related to codeblocks in doc comments in Rust documentation:

 * Allow to disable generation of `language-*` CSS classes with the `custom` attribute.
 * Add your own CSS classes to a code block so that you can use other tools to highlight them.

#### The `custom` attribute

Let's start with the new `custom` attribute: it will disable the generation of the `language-*` CSS class on the generated HTML code block. For example:

```rust
/// ```custom,c
/// int main(void) {
///     return 0;
/// }
/// ```
```

The generated HTML code block will not have `class="language-c"` because the `custom` attribute has been set. The `custom` attribute becomes especially useful with the other thing added by this feature: adding your own CSS classes.

#### Adding your own CSS classes

The second part of this feature is to allow users to add CSS classes themselves so that they can then add a JS library which will do it (like `highlight.js` or `prism.js`), allowing to support highlighting for other languages than Rust without increasing burden on rustdoc. To disable the automatic `language-*` CSS class generation, you need to use the `custom` attribute as well.

This allow users to write the following:

```rust
/// Some code block with `{class=language-c}` as the language string.
///
/// ```custom,{class=language-c}
/// int main(void) {
///     return 0;
/// }
/// ```
fn main() {}
```

This will notably produce the following HTML:

```html
<pre class="language-c">
int main(void) {
    return 0;
}</pre>
```

Instead of:

```html
<pre class="rust rust-example-rendered">
<span class="ident">int</span> <span class="ident">main</span>(<span class="ident">void</span>) {
    <span class="kw">return</span> <span class="number">0</span>;
}
</pre>
```

To be noted, we could have written `{.language-c}` to achieve the same result. `.` and `class=` have the same effect.

One last syntax point: content between parens (`(like this)`) is now considered as comment and is not taken into account at all.

In addition to this, I added an `unknown` field into `LangString` (the parsed code block "attribute") because of cases like this:

```rust
/// ```custom,class:language-c
/// main;
/// ```
pub fn foo() {}
```

Without this `unknown` field, it would generate in the DOM: `<pre class="language-class:language-c language-c">`, which is quite bad. So instead, it now stores all unknown tags into the `unknown` field and use the first one as "language". So in this case, since there is no unknown tag, it'll simply generate `<pre class="language-c">`. I added tests to cover this.

EDIT(camelid): This description is out-of-date. Using `custom,class:language-c` will generate the output `<pre class="language-class:language-c">` as would be expected; it treats `class:language-c` as just the name of a language (similar to the langstring `c` or `js` or what have you) since it does not use the designed class syntax.

Finally, I added a parser for the codeblock attributes to make it much easier to maintain. It'll be pretty easy to extend.

As to why this syntax for adding attributes was picked: it's [Pandoc's syntax](https://pandoc.org/MANUAL.html#extension-fenced_code_attributes). Even if it seems clunkier in some cases, it's extensible, and most third-party Markdown renderers are smart enough to ignore Pandoc's brace-delimited attributes (from [this comment](https://github.com/rust-lang/rust/pull/110800#issuecomment-1522044456)).

r? `@notriddle`
This commit is contained in:
bors 2024-06-01 10:18:01 +00:00
commit 05965ae238
23 changed files with 67 additions and 515 deletions

View File

@ -138,6 +138,8 @@ declare_features! (
(accepted, copy_closures, "1.26.0", Some(44490)), (accepted, copy_closures, "1.26.0", Some(44490)),
/// Allows `crate` in paths. /// Allows `crate` in paths.
(accepted, crate_in_paths, "1.30.0", Some(45477)), (accepted, crate_in_paths, "1.30.0", Some(45477)),
/// Allows users to provide classes for fenced code block using `class:classname`.
(accepted, custom_code_classes_in_docs, "CURRENT_RUSTC_VERSION", Some(79483)),
/// Allows using `#[debugger_visualizer]` attribute. /// Allows using `#[debugger_visualizer]` attribute.
(accepted, debugger_visualizer, "1.71.0", Some(95939)), (accepted, debugger_visualizer, "1.71.0", Some(95939)),
/// Allows rustc to inject a default alloc_error_handler /// Allows rustc to inject a default alloc_error_handler

View File

@ -424,8 +424,6 @@ declare_features! (
/// Allows function attribute `#[coverage(on/off)]`, to control coverage /// Allows function attribute `#[coverage(on/off)]`, to control coverage
/// instrumentation of that function. /// instrumentation of that function.
(unstable, coverage_attribute, "1.74.0", Some(84605)), (unstable, coverage_attribute, "1.74.0", Some(84605)),
/// Allows users to provide classes for fenced code block using `class:classname`.
(unstable, custom_code_classes_in_docs, "1.74.0", Some(79483)),
/// Allows non-builtin attributes in inner attribute position. /// Allows non-builtin attributes in inner attribute position.
(unstable, custom_inner_attributes, "1.30.0", Some(54726)), (unstable, custom_inner_attributes, "1.30.0", Some(54726)),
/// Allows custom test frameworks with `#![test_runner]` and `#[test_case]`. /// Allows custom test frameworks with `#![test_runner]` and `#[test_case]`.

View File

@ -624,47 +624,3 @@ add the `--scrape-tests` flag.
This flag enables the generation of links in the source code pages which allow the reader This flag enables the generation of links in the source code pages which allow the reader
to jump to a type definition. to jump to a type definition.
### Custom CSS classes for code blocks
```rust
#![feature(custom_code_classes_in_docs)]
/// ```custom,{class=language-c}
/// int main(void) { return 0; }
/// ```
pub struct Bar;
```
The text `int main(void) { return 0; }` is rendered without highlighting in a code block
with the class `language-c`. This can be used to highlight other languages through JavaScript
libraries for example.
Without the `custom` attribute, it would be generated as a Rust code example with an additional
`language-C` CSS class. Therefore, if you specifically don't want it to be a Rust code example,
don't forget to add the `custom` attribute.
To be noted that you can replace `class=` with `.` to achieve the same result:
```rust
#![feature(custom_code_classes_in_docs)]
/// ```custom,{.language-c}
/// int main(void) { return 0; }
/// ```
pub struct Bar;
```
To be noted, `rust` and `.rust`/`class=rust` have different effects: `rust` indicates that this is
a Rust code block whereas the two others add a "rust" CSS class on the code block.
You can also use double quotes:
```rust
#![feature(custom_code_classes_in_docs)]
/// ```"not rust" {."hello everyone"}
/// int main(void) { return 0; }
/// ```
pub struct Bar;
```

View File

@ -376,6 +376,44 @@ that the code sample should be compiled using the respective edition of Rust.
# fn foo() {} # fn foo() {}
``` ```
### Custom CSS classes for code blocks
```rust
/// ```custom,{class=language-c}
/// int main(void) { return 0; }
/// ```
pub struct Bar;
```
The text `int main(void) { return 0; }` is rendered without highlighting in a code block
with the class `language-c`. This can be used to highlight other languages through JavaScript
libraries for example.
Without the `custom` attribute, it would be generated as a Rust code example with an additional
`language-C` CSS class. Therefore, if you specifically don't want it to be a Rust code example,
don't forget to add the `custom` attribute.
To be noted that you can replace `class=` with `.` to achieve the same result:
```rust
/// ```custom,{.language-c}
/// int main(void) { return 0; }
/// ```
pub struct Bar;
```
To be noted, `rust` and `.rust`/`class=rust` have different effects: `rust` indicates that this is
a Rust code block whereas the two others add a "rust" CSS class on the code block.
You can also use double quotes:
```rust
/// ```"not rust" {."hello everyone"}
/// int main(void) { return 0; }
/// ```
pub struct Bar;
```
## Syntax reference ## Syntax reference
The *exact* syntax for code blocks, including the edge cases, can be found The *exact* syntax for code blocks, including the edge cases, can be found

View File

@ -1343,7 +1343,6 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
def_id.to_def_id(), def_id.to_def_id(),
span_of_fragments(&attrs.doc_strings).unwrap_or(sp), span_of_fragments(&attrs.doc_strings).unwrap_or(sp),
)), )),
self.tcx.features().custom_code_classes_in_docs,
); );
} }

View File

@ -46,8 +46,6 @@ impl ExternalHtml {
edition, edition,
playground, playground,
heading_offset: HeadingOffset::H2, heading_offset: HeadingOffset::H2,
// For external files, it'll be disabled until the feature is enabled by default.
custom_code_classes_in_docs: false,
} }
.into_string() .into_string()
); );
@ -63,8 +61,6 @@ impl ExternalHtml {
edition, edition,
playground, playground,
heading_offset: HeadingOffset::H2, heading_offset: HeadingOffset::H2,
// For external files, it'll be disabled until the feature is enabled by default.
custom_code_classes_in_docs: false,
} }
.into_string() .into_string()
); );

View File

@ -20,7 +20,6 @@
//! edition: Edition::Edition2015, //! edition: Edition::Edition2015,
//! playground: &None, //! playground: &None,
//! heading_offset: HeadingOffset::H2, //! heading_offset: HeadingOffset::H2,
//! custom_code_classes_in_docs: true,
//! }; //! };
//! let html = md.into_string(); //! let html = md.into_string();
//! // ... something using html //! // ... something using html
@ -97,8 +96,6 @@ pub struct Markdown<'a> {
/// Offset at which we render headings. /// Offset at which we render headings.
/// E.g. if `heading_offset: HeadingOffset::H2`, then `# something` renders an `<h2>`. /// E.g. if `heading_offset: HeadingOffset::H2`, then `# something` renders an `<h2>`.
pub heading_offset: HeadingOffset, 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. /// A struct like `Markdown` that renders the markdown with a table of contents.
pub(crate) struct MarkdownWithToc<'a> { pub(crate) struct MarkdownWithToc<'a> {
@ -107,8 +104,6 @@ pub(crate) struct MarkdownWithToc<'a> {
pub(crate) error_codes: ErrorCodes, pub(crate) error_codes: ErrorCodes,
pub(crate) edition: Edition, pub(crate) edition: Edition,
pub(crate) playground: &'a Option<Playground>, 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 /// A tuple struct like `Markdown` that renders the markdown escaping HTML tags
/// and includes no paragraph tags. /// and includes no paragraph tags.
@ -209,7 +204,6 @@ struct CodeBlocks<'p, 'a, I: Iterator<Item = Event<'a>>> {
// Information about the playground if a URL has been specified, containing an // Information about the playground if a URL has been specified, containing an
// optional crate name and the URL. // optional crate name and the URL.
playground: &'p Option<Playground>, playground: &'p Option<Playground>,
custom_code_classes_in_docs: bool,
} }
impl<'p, 'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'p, 'a, I> { impl<'p, 'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'p, 'a, I> {
@ -218,15 +212,8 @@ impl<'p, 'a, I: Iterator<Item = Event<'a>>> CodeBlocks<'p, 'a, I> {
error_codes: ErrorCodes, error_codes: ErrorCodes,
edition: Edition, edition: Edition,
playground: &'p Option<Playground>, playground: &'p Option<Playground>,
custom_code_classes_in_docs: bool,
) -> Self { ) -> Self {
CodeBlocks { CodeBlocks { inner: iter, check_error_codes: error_codes, edition, playground }
inner: iter,
check_error_codes: error_codes,
edition,
playground,
custom_code_classes_in_docs,
}
} }
} }
@ -253,12 +240,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
let LangString { added_classes, compile_fail, should_panic, ignore, edition, .. } = let LangString { added_classes, compile_fail, should_panic, ignore, edition, .. } =
match kind { match kind {
CodeBlockKind::Fenced(ref lang) => { CodeBlockKind::Fenced(ref lang) => {
let parse_result = LangString::parse_without_check( let parse_result =
lang, LangString::parse_without_check(lang, self.check_error_codes, false);
self.check_error_codes,
false,
self.custom_code_classes_in_docs,
);
if !parse_result.rust { if !parse_result.rust {
let added_classes = parse_result.added_classes; let added_classes = parse_result.added_classes;
let lang_string = if let Some(lang) = parse_result.unknown.first() { let lang_string = if let Some(lang) = parse_result.unknown.first() {
@ -733,17 +716,8 @@ pub(crate) fn find_testable_code<T: doctest::Tester>(
error_codes: ErrorCodes, error_codes: ErrorCodes,
enable_per_target_ignores: bool, enable_per_target_ignores: bool,
extra_info: Option<&ExtraInfo<'_>>, extra_info: Option<&ExtraInfo<'_>>,
custom_code_classes_in_docs: bool,
) { ) {
find_codes( find_codes(doc, tests, error_codes, enable_per_target_ignores, extra_info, false)
doc,
tests,
error_codes,
enable_per_target_ignores,
extra_info,
false,
custom_code_classes_in_docs,
)
} }
pub(crate) fn find_codes<T: doctest::Tester>( pub(crate) fn find_codes<T: doctest::Tester>(
@ -753,7 +727,6 @@ pub(crate) fn find_codes<T: doctest::Tester>(
enable_per_target_ignores: bool, enable_per_target_ignores: bool,
extra_info: Option<&ExtraInfo<'_>>, extra_info: Option<&ExtraInfo<'_>>,
include_non_rust: bool, include_non_rust: bool,
custom_code_classes_in_docs: bool,
) { ) {
let mut parser = Parser::new(doc).into_offset_iter(); let mut parser = Parser::new(doc).into_offset_iter();
let mut prev_offset = 0; let mut prev_offset = 0;
@ -772,7 +745,6 @@ pub(crate) fn find_codes<T: doctest::Tester>(
error_codes, error_codes,
enable_per_target_ignores, enable_per_target_ignores,
extra_info, extra_info,
custom_code_classes_in_docs,
) )
} }
} }
@ -1170,29 +1142,6 @@ impl<'a, 'tcx> Iterator for TagIterator<'a, 'tcx> {
} }
} }
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 { impl Default for LangString {
fn default() -> Self { fn default() -> Self {
Self { Self {
@ -1216,15 +1165,8 @@ impl LangString {
string: &str, string: &str,
allow_error_code_check: ErrorCodes, allow_error_code_check: ErrorCodes,
enable_per_target_ignores: bool, enable_per_target_ignores: bool,
custom_code_classes_in_docs: bool,
) -> Self { ) -> Self {
Self::parse( Self::parse(string, allow_error_code_check, enable_per_target_ignores, None)
string,
allow_error_code_check,
enable_per_target_ignores,
None,
custom_code_classes_in_docs,
)
} }
fn parse( fn parse(
@ -1232,7 +1174,6 @@ impl LangString {
allow_error_code_check: ErrorCodes, allow_error_code_check: ErrorCodes,
enable_per_target_ignores: bool, enable_per_target_ignores: bool,
extra: Option<&ExtraInfo<'_>>, extra: Option<&ExtraInfo<'_>>,
custom_code_classes_in_docs: bool,
) -> Self { ) -> Self {
let allow_error_code_check = allow_error_code_check.as_bool(); let allow_error_code_check = allow_error_code_check.as_bool();
let mut seen_rust_tags = false; let mut seen_rust_tags = false;
@ -1269,11 +1210,7 @@ impl LangString {
seen_rust_tags = true; seen_rust_tags = true;
} }
LangStringToken::LangToken("custom") => { LangStringToken::LangToken("custom") => {
if custom_code_classes_in_docs {
seen_custom_tag = true; seen_custom_tag = true;
} else {
seen_other_tags = true;
}
} }
LangStringToken::LangToken("test_harness") => { LangStringToken::LangToken("test_harness") => {
data.test_harness = true; data.test_harness = true;
@ -1364,7 +1301,6 @@ impl LangString {
data.unknown.push(x.to_owned()); data.unknown.push(x.to_owned());
} }
LangStringToken::KeyValueAttribute(key, value) => { LangStringToken::KeyValueAttribute(key, value) => {
if custom_code_classes_in_docs {
if key == "class" { if key == "class" {
data.added_classes.push(value.to_owned()); data.added_classes.push(value.to_owned());
} else if let Some(extra) = extra { } else if let Some(extra) = extra {
@ -1372,9 +1308,6 @@ impl LangString {
"unsupported attribute `{key}`" "unsupported attribute `{key}`"
)); ));
} }
} else {
seen_other_tags = true;
}
} }
LangStringToken::ClassAttribute(class) => { LangStringToken::ClassAttribute(class) => {
data.added_classes.push(class.to_owned()); data.added_classes.push(class.to_owned());
@ -1383,11 +1316,7 @@ impl LangString {
} }
}; };
if custom_code_classes_in_docs { call(&mut TagIterator::new(string, extra));
call(&mut TagIterator::new(string, extra))
} else {
call(&mut tokens(string))
}
// ignore-foo overrides ignore // ignore-foo overrides ignore
if !ignores.is_empty() { if !ignores.is_empty() {
@ -1410,7 +1339,6 @@ impl Markdown<'_> {
edition, edition,
playground, playground,
heading_offset, heading_offset,
custom_code_classes_in_docs,
} = self; } = self;
// This is actually common enough to special-case // This is actually common enough to special-case
@ -1433,7 +1361,7 @@ impl Markdown<'_> {
let p = Footnotes::new(p); let p = Footnotes::new(p);
let p = LinkReplacer::new(p.map(|(ev, _)| ev), links); let p = LinkReplacer::new(p.map(|(ev, _)| ev), links);
let p = TableWrapper::new(p); let p = TableWrapper::new(p);
let p = CodeBlocks::new(p, codes, edition, playground, custom_code_classes_in_docs); let p = CodeBlocks::new(p, codes, edition, playground);
html::push_html(&mut s, p); html::push_html(&mut s, p);
s s
@ -1442,14 +1370,7 @@ impl Markdown<'_> {
impl MarkdownWithToc<'_> { impl MarkdownWithToc<'_> {
pub(crate) fn into_string(self) -> String { pub(crate) fn into_string(self) -> String {
let MarkdownWithToc { let MarkdownWithToc { content: md, ids, error_codes: codes, edition, playground } = self;
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(); let p = Parser::new_ext(md, main_body_opts()).into_offset_iter();
@ -1461,7 +1382,7 @@ impl MarkdownWithToc<'_> {
let p = HeadingLinks::new(p, Some(&mut toc), ids, HeadingOffset::H1); let p = HeadingLinks::new(p, Some(&mut toc), ids, HeadingOffset::H1);
let p = Footnotes::new(p); let p = Footnotes::new(p);
let p = TableWrapper::new(p.map(|(ev, _)| ev)); let p = TableWrapper::new(p.map(|(ev, _)| ev));
let p = CodeBlocks::new(p, codes, edition, playground, custom_code_classes_in_docs); let p = CodeBlocks::new(p, codes, edition, playground);
html::push_html(&mut s, p); html::push_html(&mut s, p);
} }
@ -1902,11 +1823,7 @@ pub(crate) struct RustCodeBlock {
/// Returns a range of bytes for each code block in the markdown that is tagged as `rust` or /// Returns a range of bytes for each code block in the markdown that is tagged as `rust` or
/// untagged (and assumed to be rust). /// untagged (and assumed to be rust).
pub(crate) fn rust_code_blocks( pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec<RustCodeBlock> {
md: &str,
extra_info: &ExtraInfo<'_>,
custom_code_classes_in_docs: bool,
) -> Vec<RustCodeBlock> {
let mut code_blocks = vec![]; let mut code_blocks = vec![];
if md.is_empty() { if md.is_empty() {
@ -1923,13 +1840,7 @@ pub(crate) fn rust_code_blocks(
let lang_string = if syntax.is_empty() { let lang_string = if syntax.is_empty() {
Default::default() Default::default()
} else { } else {
LangString::parse( LangString::parse(&*syntax, ErrorCodes::Yes, false, Some(extra_info))
&*syntax,
ErrorCodes::Yes,
false,
Some(extra_info),
custom_code_classes_in_docs,
)
}; };
if !lang_string.rust { if !lang_string.rust {
continue; continue;

View File

@ -49,7 +49,7 @@ fn test_unique_id() {
fn test_lang_string_parse() { fn test_lang_string_parse() {
fn t(lg: LangString) { fn t(lg: LangString) {
let s = &lg.original; let s = &lg.original;
assert_eq!(LangString::parse(s, ErrorCodes::Yes, true, None, true), lg) assert_eq!(LangString::parse(s, ErrorCodes::Yes, true, None), lg)
} }
t(Default::default()); t(Default::default());
@ -305,7 +305,6 @@ fn test_header() {
edition: DEFAULT_EDITION, edition: DEFAULT_EDITION,
playground: &None, playground: &None,
heading_offset: HeadingOffset::H2, heading_offset: HeadingOffset::H2,
custom_code_classes_in_docs: true,
} }
.into_string(); .into_string();
assert_eq!(output, expect, "original: {}", input); assert_eq!(output, expect, "original: {}", input);
@ -357,7 +356,6 @@ fn test_header_ids_multiple_blocks() {
edition: DEFAULT_EDITION, edition: DEFAULT_EDITION,
playground: &None, playground: &None,
heading_offset: HeadingOffset::H2, heading_offset: HeadingOffset::H2,
custom_code_classes_in_docs: true,
} }
.into_string(); .into_string();
assert_eq!(output, expect, "original: {}", input); assert_eq!(output, expect, "original: {}", input);
@ -481,7 +479,7 @@ fn test_markdown_html_escape() {
fn test_find_testable_code_line() { fn test_find_testable_code_line() {
fn t(input: &str, expect: &[usize]) { fn t(input: &str, expect: &[usize]) {
let mut lines = Vec::<usize>::new(); let mut lines = Vec::<usize>::new();
find_testable_code(input, &mut lines, ErrorCodes::No, false, None, true); find_testable_code(input, &mut lines, ErrorCodes::No, false, None);
assert_eq!(lines, expect); assert_eq!(lines, expect);
} }
@ -506,7 +504,6 @@ fn test_ascii_with_prepending_hashtag() {
edition: DEFAULT_EDITION, edition: DEFAULT_EDITION,
playground: &None, playground: &None,
heading_offset: HeadingOffset::H2, heading_offset: HeadingOffset::H2,
custom_code_classes_in_docs: true,
} }
.into_string(); .into_string();
assert_eq!(output, expect, "original: {}", input); assert_eq!(output, expect, "original: {}", input);

View File

@ -506,7 +506,6 @@ fn scrape_examples_help(shared: &SharedContext<'_>) -> String {
edition: shared.edition(), edition: shared.edition(),
playground: &shared.playground, playground: &shared.playground,
heading_offset: HeadingOffset::H1, heading_offset: HeadingOffset::H1,
custom_code_classes_in_docs: false,
} }
.into_string() .into_string()
) )
@ -540,7 +539,6 @@ fn render_markdown<'a, 'cx: 'a>(
heading_offset: HeadingOffset, heading_offset: HeadingOffset,
) -> impl fmt::Display + 'a + Captures<'cx> { ) -> impl fmt::Display + 'a + Captures<'cx> {
display_fn(move |f| { display_fn(move |f| {
let custom_code_classes_in_docs = cx.tcx().features().custom_code_classes_in_docs;
write!( write!(
f, f,
"<div class=\"docblock\">{}</div>", "<div class=\"docblock\">{}</div>",
@ -552,7 +550,6 @@ fn render_markdown<'a, 'cx: 'a>(
edition: cx.shared.edition(), edition: cx.shared.edition(),
playground: &cx.shared.playground, playground: &cx.shared.playground,
heading_offset, heading_offset,
custom_code_classes_in_docs,
} }
.into_string() .into_string()
) )
@ -1885,7 +1882,6 @@ fn render_impl(
</div>", </div>",
); );
} }
let custom_code_classes_in_docs = cx.tcx().features().custom_code_classes_in_docs;
write!( write!(
w, w,
"<div class=\"docblock\">{}</div>", "<div class=\"docblock\">{}</div>",
@ -1897,7 +1893,6 @@ fn render_impl(
edition: cx.shared.edition(), edition: cx.shared.edition(),
playground: &cx.shared.playground, playground: &cx.shared.playground,
heading_offset: HeadingOffset::H4, heading_offset: HeadingOffset::H4,
custom_code_classes_in_docs,
} }
.into_string() .into_string()
); );

View File

@ -82,8 +82,6 @@ pub(crate) fn render<P: AsRef<Path>>(
error_codes, error_codes,
edition, edition,
playground: &playground, playground: &playground,
// For markdown files, it'll be disabled until the feature is enabled by default.
custom_code_classes_in_docs: false,
} }
.into_string() .into_string()
} else { } else {
@ -95,8 +93,6 @@ pub(crate) fn render<P: AsRef<Path>>(
edition, edition,
playground: &playground, playground: &playground,
heading_offset: HeadingOffset::H1, heading_offset: HeadingOffset::H1,
// For markdown files, it'll be disabled until the feature is enabled by default.
custom_code_classes_in_docs: false,
} }
.into_string() .into_string()
}; };
@ -174,14 +170,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
let codes = ErrorCodes::from(options.unstable_features.is_nightly_build()); let codes = ErrorCodes::from(options.unstable_features.is_nightly_build());
// For markdown files, custom code classes will be disabled until the feature is enabled by default. // For markdown files, custom code classes will be disabled until the feature is enabled by default.
find_testable_code( find_testable_code(&input_str, &mut collector, codes, options.enable_per_target_ignores, None);
&input_str,
&mut collector,
codes,
options.enable_per_target_ignores,
None,
false,
);
crate::doctest::run_tests(options.test_args, options.nocapture, collector.tests); crate::doctest::run_tests(options.test_args, options.nocapture, collector.tests);
Ok(()) Ok(())

View File

@ -208,14 +208,7 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> {
let has_docs = !i.attrs.doc_strings.is_empty(); let has_docs = !i.attrs.doc_strings.is_empty();
let mut tests = Tests { found_tests: 0 }; let mut tests = Tests { found_tests: 0 };
find_testable_code( find_testable_code(&i.doc_value(), &mut tests, ErrorCodes::No, false, None);
&i.doc_value(),
&mut tests,
ErrorCodes::No,
false,
None,
self.ctx.tcx.features().custom_code_classes_in_docs,
);
let has_doc_example = tests.found_tests != 0; let has_doc_example = tests.found_tests != 0;
let hir_id = DocContext::as_local_hir_id(self.ctx.tcx, i.item_id).unwrap(); let hir_id = DocContext::as_local_hir_id(self.ctx.tcx, i.item_id).unwrap();

View File

@ -1,93 +0,0 @@
//! NIGHTLY & UNSTABLE CHECK: custom_code_classes_in_docs
//!
//! This pass will produce errors when finding custom classes outside of
//! nightly + relevant feature active.
use super::Pass;
use crate::clean::{Crate, Item};
use crate::core::DocContext;
use crate::fold::DocFolder;
use crate::html::markdown::{find_codes, ErrorCodes, LangString};
use rustc_errors::StashKey;
use rustc_feature::GateIssue;
use rustc_session::parse::add_feature_diagnostics_for_issue;
use rustc_span::symbol::sym;
pub(crate) const CHECK_CUSTOM_CODE_CLASSES: Pass = Pass {
name: "check-custom-code-classes",
run: check_custom_code_classes,
description: "check for custom code classes without the feature-gate enabled",
};
pub(crate) fn check_custom_code_classes(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
if cx.tcx.features().custom_code_classes_in_docs {
// Nothing to check here if the feature is enabled.
return krate;
}
let mut coll = CustomCodeClassLinter { cx };
coll.fold_crate(krate)
}
struct CustomCodeClassLinter<'a, 'tcx> {
cx: &'a DocContext<'tcx>,
}
impl<'a, 'tcx> DocFolder for CustomCodeClassLinter<'a, 'tcx> {
fn fold_item(&mut self, item: Item) -> Option<Item> {
look_for_custom_classes(&self.cx, &item);
Some(self.fold_item_recur(item))
}
}
#[derive(Debug)]
struct TestsWithCustomClasses {
custom_classes_found: Vec<String>,
}
impl crate::doctest::Tester for TestsWithCustomClasses {
fn add_test(&mut self, _: String, config: LangString, _: usize) {
self.custom_classes_found.extend(config.added_classes);
}
}
pub(crate) fn look_for_custom_classes<'tcx>(cx: &DocContext<'tcx>, item: &Item) {
if !item.item_id.is_local() {
// If non-local, no need to check anything.
return;
}
let mut tests = TestsWithCustomClasses { custom_classes_found: vec![] };
let dox = item.attrs.doc_value();
find_codes(&dox, &mut tests, ErrorCodes::No, false, None, true, true);
if !tests.custom_classes_found.is_empty() {
let span = item.attr_span(cx.tcx);
let sess = &cx.tcx.sess;
let mut err = sess
.dcx()
.struct_span_warn(span, "custom classes in code blocks will change behaviour");
add_feature_diagnostics_for_issue(
&mut err,
sess,
sym::custom_code_classes_in_docs,
GateIssue::Language,
false,
None,
);
err.note(
// This will list the wrong items to make them more easily searchable.
// To ensure the most correct hits, it adds back the 'class:' that was stripped.
format!(
"found these custom classes: class={}",
tests.custom_classes_found.join(",class=")
),
);
// A later feature_err call can steal and cancel this warning.
err.stash(span, StashKey::EarlySyntaxWarning);
}
}

View File

@ -112,14 +112,7 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item
let mut tests = Tests { found_tests: 0 }; let mut tests = Tests { found_tests: 0 };
find_testable_code( find_testable_code(dox, &mut tests, ErrorCodes::No, false, None);
dox,
&mut tests,
ErrorCodes::No,
false,
None,
cx.tcx.features().custom_code_classes_in_docs,
);
if tests.found_tests == 0 && cx.tcx.features().rustdoc_missing_doc_code_examples { if tests.found_tests == 0 && cx.tcx.features().rustdoc_missing_doc_code_examples {
if should_have_doc_example(cx, item) { if should_have_doc_example(cx, item) {

View File

@ -20,9 +20,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &clean::Item) {
if let Some(dox) = &item.opt_doc_value() { if let Some(dox) = &item.opt_doc_value() {
let sp = item.attr_span(cx.tcx); let sp = item.attr_span(cx.tcx);
let extra = crate::html::markdown::ExtraInfo::new(cx.tcx, item.item_id.expect_def_id(), sp); let extra = crate::html::markdown::ExtraInfo::new(cx.tcx, item.item_id.expect_def_id(), sp);
for code_block in for code_block in markdown::rust_code_blocks(dox, &extra) {
markdown::rust_code_blocks(dox, &extra, cx.tcx.features().custom_code_classes_in_docs)
{
check_rust_syntax(cx, item, dox, code_block); check_rust_syntax(cx, item, dox, code_block);
} }
} }

View File

@ -38,9 +38,6 @@ pub(crate) use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE;
mod lint; mod lint;
pub(crate) use self::lint::RUN_LINTS; pub(crate) use self::lint::RUN_LINTS;
mod check_custom_code_classes;
pub(crate) use self::check_custom_code_classes::CHECK_CUSTOM_CODE_CLASSES;
/// A single pass over the cleaned documentation. /// A single pass over the cleaned documentation.
/// ///
/// Runs in the compiler context, so it has access to types and traits and the like. /// Runs in the compiler context, so it has access to types and traits and the like.
@ -72,7 +69,6 @@ pub(crate) enum Condition {
/// The full list of passes. /// The full list of passes.
pub(crate) const PASSES: &[Pass] = &[ pub(crate) const PASSES: &[Pass] = &[
CHECK_CUSTOM_CODE_CLASSES,
CHECK_DOC_TEST_VISIBILITY, CHECK_DOC_TEST_VISIBILITY,
STRIP_ALIASED_NON_LOCAL, STRIP_ALIASED_NON_LOCAL,
STRIP_HIDDEN, STRIP_HIDDEN,
@ -87,7 +83,6 @@ pub(crate) const PASSES: &[Pass] = &[
/// The list of passes run by default. /// The list of passes run by default.
pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[ pub(crate) const DEFAULT_PASSES: &[ConditionalPass] = &[
ConditionalPass::always(CHECK_CUSTOM_CODE_CLASSES),
ConditionalPass::always(COLLECT_TRAIT_IMPLS), ConditionalPass::always(COLLECT_TRAIT_IMPLS),
ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY), ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY),
ConditionalPass::always(STRIP_ALIASED_NON_LOCAL), ConditionalPass::always(STRIP_ALIASED_NON_LOCAL),

View File

@ -1,83 +0,0 @@
// 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]
/// ```{. }
/// main;
/// ```
//~^^^ ERROR unexpected ` ` character after `.`
pub fn foo() {}
/// ```{class= a}
/// main;
/// ```
//~^^^ ERROR unexpected ` ` character after `=`
pub fn foo2() {}
/// ```{#id}
/// main;
/// ```
//~^^^ ERROR unexpected character `#`
pub fn foo3() {}
/// ```{{
/// main;
/// ```
//~^^^ ERROR unexpected character `{`
pub fn foo4() {}
/// ```}
/// main;
/// ```
//~^^^ ERROR unexpected character `}`
pub fn foo5() {}
/// ```)
/// main;
/// ```
//~^^^ ERROR unexpected character `)`
pub fn foo6() {}
/// ```{class=}
/// main;
/// ```
//~^^^ ERROR unexpected `}` character after `=`
pub fn foo7() {}
/// ```(
/// main;
/// ```
//~^^^ ERROR unclosed comment: missing `)` at the end
pub fn foo8() {}
/// ```{class=one=two}
/// main;
/// ```
//~^^^ ERROR unexpected `=` character
pub fn foo9() {}
/// ```{.one.two}
/// main;
/// ```
pub fn foo10() {}
/// ```{class=(one}
/// main;
/// ```
//~^^^ ERROR unexpected `(` character after `=`
pub fn foo11() {}
/// ```{class=one.two}
/// main;
/// ```
pub fn foo12() {}
/// ```{(comment)}
/// main;
/// ```
//~^^^ ERROR unexpected character `(`
pub fn foo13() {}

View File

@ -1,97 +0,0 @@
error: unexpected ` ` character after `.`
--> $DIR/custom_code_classes_in_docs-warning.rs:9:1
|
LL | / /// ```{. }
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: unexpected ` ` character after `=`
--> $DIR/custom_code_classes_in_docs-warning.rs:15:1
|
LL | / /// ```{class= a}
LL | | /// main;
LL | | /// ```
| |_______^
error: unexpected character `#`
--> $DIR/custom_code_classes_in_docs-warning.rs:21:1
|
LL | / /// ```{#id}
LL | | /// main;
LL | | /// ```
| |_______^
error: unexpected character `{`
--> $DIR/custom_code_classes_in_docs-warning.rs:27:1
|
LL | / /// ```{{
LL | | /// main;
LL | | /// ```
| |_______^
error: unexpected character `}`
--> $DIR/custom_code_classes_in_docs-warning.rs:33:1
|
LL | / /// ```}
LL | | /// main;
LL | | /// ```
| |_______^
error: unexpected character `)`
--> $DIR/custom_code_classes_in_docs-warning.rs:39:1
|
LL | / /// ```)
LL | | /// main;
LL | | /// ```
| |_______^
error: unexpected `}` character after `=`
--> $DIR/custom_code_classes_in_docs-warning.rs:45:1
|
LL | / /// ```{class=}
LL | | /// main;
LL | | /// ```
| |_______^
error: unclosed comment: missing `)` at the end
--> $DIR/custom_code_classes_in_docs-warning.rs:51:1
|
LL | / /// ```(
LL | | /// main;
LL | | /// ```
| |_______^
error: unexpected `=` character
--> $DIR/custom_code_classes_in_docs-warning.rs:57:1
|
LL | / /// ```{class=one=two}
LL | | /// main;
LL | | /// ```
| |_______^
error: unexpected `(` character after `=`
--> $DIR/custom_code_classes_in_docs-warning.rs:68:1
|
LL | / /// ```{class=(one}
LL | | /// main;
LL | | /// ```
| |_______^
error: unexpected character `(`
--> $DIR/custom_code_classes_in_docs-warning.rs:79:1
|
LL | / /// ```{(comment)}
LL | | /// main;
LL | | /// ```
| |_______^
error: aborting due to 11 previous errors

View File

@ -1,7 +1,6 @@
// This test ensures that warnings are working as expected for "custom_code_classes_in_docs" // This test ensures that warnings are working as expected for "custom_code_classes_in_docs"
// feature. // feature.
#![feature(custom_code_classes_in_docs)]
#![deny(warnings)] #![deny(warnings)]
#![feature(no_core)] #![feature(no_core)]
#![no_core] #![no_core]

View File

@ -1,5 +1,5 @@
error: unclosed quote string `"` error: unclosed quote string `"`
--> $DIR/custom_code_classes_in_docs-warning3.rs:9:1 --> $DIR/custom_code_classes_in_docs-warning3.rs:8:1
| |
LL | / /// ```{class="} LL | / /// ```{class="}
LL | | /// main; LL | | /// main;
@ -11,14 +11,14 @@ LL | | /// ```
| |_______^ | |_______^
| |
note: the lint level is defined here note: the lint level is defined here
--> $DIR/custom_code_classes_in_docs-warning3.rs:5:9 --> $DIR/custom_code_classes_in_docs-warning3.rs:4:9
| |
LL | #![deny(warnings)] LL | #![deny(warnings)]
| ^^^^^^^^ | ^^^^^^^^
= note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]` = note: `#[deny(rustdoc::invalid_codeblock_attributes)]` implied by `#[deny(warnings)]`
error: unclosed quote string `"` error: unclosed quote string `"`
--> $DIR/custom_code_classes_in_docs-warning3.rs:9:1 --> $DIR/custom_code_classes_in_docs-warning3.rs:8:1
| |
LL | / /// ```{class="} LL | / /// ```{class="}
LL | | /// main; LL | | /// main;

View File

@ -1,16 +0,0 @@
//@ check-pass
/// ```{class=language-c}
/// int main(void) { return 0; }
/// ```
//~^^^ WARNING custom classes in code blocks will change behaviour
//~| NOTE found these custom classes: class=language-c
//~| NOTE see issue #79483 <https://github.com/rust-lang/rust/issues/79483>
//~| NOTE: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
//~| HELP add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable
pub struct Bar;
/// ```ASN.1
/// int main(void) { return 0; }
/// ```
pub struct Bar2;

View File

@ -1,15 +0,0 @@
warning: custom classes in code blocks will change behaviour
--> $DIR/feature-gate-custom_code_classes_in_docs.rs:3:1
|
LL | / /// ```{class=language-c}
LL | | /// int main(void) { return 0; }
LL | | /// ```
| |_______^
|
= note: see issue #79483 <https://github.com/rust-lang/rust/issues/79483> for more information
= help: add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
= note: found these custom classes: class=language-c
warning: 1 warning emitted

View File

@ -1,5 +1,4 @@
Available passes for running rustdoc: 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 check_doc_test_visibility - run various visibility-related lints on doctests
strip-aliased-non-local - strips all non-local private aliased items from the output strip-aliased-non-local - strips all non-local private aliased items from the output
strip-hidden - strips all `#[doc(hidden)]` items from the output strip-hidden - strips all `#[doc(hidden)]` items from the output
@ -12,7 +11,6 @@ calculate-doc-coverage - counts the number of items with and without documentati
run-lints - runs some of rustdoc's lints run-lints - runs some of rustdoc's lints
Default passes for rustdoc: Default passes for rustdoc:
check-custom-code-classes
collect-trait-impls collect-trait-impls
check_doc_test_visibility check_doc_test_visibility
strip-aliased-non-local strip-aliased-non-local

View File

@ -1,6 +1,5 @@
// Test for `custom_code_classes_in_docs` feature. // Test for `custom_code_classes_in_docs` feature.
#![feature(custom_code_classes_in_docs)]
#![crate_name = "foo"] #![crate_name = "foo"]
#![feature(no_core)] #![feature(no_core)]
#![no_core] #![no_core]