Stabilize custom_code_classes_in_docs feature

This commit is contained in:
Guillaume Gomez 2024-05-01 15:24:32 +02:00
parent 378a43a065
commit 2f6abd190d
23 changed files with 67 additions and 515 deletions

View File

@ -138,6 +138,8 @@ macro_rules! 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 @@ pub fn internal(&self, feature: Symbol) -> bool {
/// 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

@ -1345,7 +1345,6 @@ fn visit_testable<F: FnOnce(&mut Self)>(
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 @@ pub(crate) fn load(
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 @@ pub(crate) fn load(
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 @@ fn new(
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 @@ fn next(&mut self) -> Option<Self::Item> {
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,
) )
} }
} }
@ -1167,29 +1139,6 @@ fn next(&mut self) -> Option<Self::Item> {
} }
} }
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 {
@ -1213,15 +1162,8 @@ fn parse_without_check(
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(
@ -1229,7 +1171,6 @@ fn parse(
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;
@ -1266,11 +1207,7 @@ fn parse(
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;
@ -1361,16 +1298,12 @@ fn parse(
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 { extra.error_invalid_codeblock_attr(format!(
extra.error_invalid_codeblock_attr(format!( "unsupported attribute `{key}`"
"unsupported attribute `{key}`" ));
));
}
} else {
seen_other_tags = true;
} }
} }
LangStringToken::ClassAttribute(class) => { LangStringToken::ClassAttribute(class) => {
@ -1380,11 +1313,7 @@ fn parse(
} }
}; };
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() {
@ -1407,7 +1336,6 @@ pub fn into_string(self) -> String {
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
@ -1430,7 +1358,7 @@ pub fn into_string(self) -> String {
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
@ -1439,14 +1367,7 @@ pub fn into_string(self) -> String {
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();
@ -1458,7 +1379,7 @@ pub(crate) fn into_string(self) -> String {
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);
} }
@ -1899,11 +1820,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() {
@ -1920,13 +1837,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 t(input: &str, expect: &str) {
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 t(map: &mut IdMap, input: &str, expect: &str) {
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 t(input: &str, expect: &str) {
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 t(input: &str, expect: &str) {
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

@ -504,7 +504,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()
) )
@ -538,7 +537,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>",
@ -550,7 +548,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()
) )
@ -1868,7 +1865,6 @@ fn render_default_items(
</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>",
@ -1880,7 +1876,6 @@ fn render_default_items(
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()
}; };
@ -168,14 +164,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 @@ fn visit_item(&mut self, i: &clean::Item) {
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

@ -35,9 +35,6 @@
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.
@ -69,7 +66,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_HIDDEN, STRIP_HIDDEN,
STRIP_PRIVATE, STRIP_PRIVATE,
@ -83,7 +79,6 @@ pub(crate) enum Condition {
/// 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::new(STRIP_HIDDEN, WhenNotDocumentHidden), ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),

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-hidden - strips all `#[doc(hidden)]` items from the output 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 strip-private - strips all private items from a crate which cannot be seen externally, implies strip-priv-imports
@ -11,7 +10,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-hidden (when not --document-hidden-items) strip-hidden (when not --document-hidden-items)

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]