From e1294b26af2d7f731381ef18623f78f39a67f5d7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 17 Sep 2023 14:52:45 +0200 Subject: [PATCH 1/3] Don't emit an error if the `custom_code_classes_in_docs` feature is disabled when its syntax is used. --- src/librustdoc/doctest.rs | 1 + src/librustdoc/externalfiles.rs | 4 + src/librustdoc/html/markdown.rs | 95 +++++++++++++++---- src/librustdoc/html/render/mod.rs | 9 +- src/librustdoc/markdown.rs | 14 ++- .../passes/calculate_doc_coverage.rs | 9 +- .../passes/check_custom_code_classes.rs | 31 ++++-- .../passes/check_doc_test_visibility.rs | 9 +- .../passes/lint/check_code_block_syntax.rs | 4 +- 9 files changed, 144 insertions(+), 32 deletions(-) diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index ea87268877fb9..24597c3aca312 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -1243,6 +1243,7 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> { def_id.to_def_id(), span_of_fragments(&attrs.doc_strings).unwrap_or(sp), )), + self.tcx.features().custom_code_classes_in_docs, ); } diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs index f0ebb8e5a3905..b34b69b1f1510 100644 --- a/src/librustdoc/externalfiles.rs +++ b/src/librustdoc/externalfiles.rs @@ -46,6 +46,8 @@ impl ExternalHtml { edition, playground, 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() ); @@ -61,6 +63,8 @@ impl ExternalHtml { edition, playground, 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() ); diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 177fb1a9426f9..59958fbaef918 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -20,6 +20,7 @@ //! edition: Edition::Edition2015, //! playground: &None, //! heading_offset: HeadingOffset::H2, +//! custom_code_classes_in_docs: true, //! }; //! let html = md.into_string(); //! // ... something using html @@ -95,6 +96,8 @@ pub struct Markdown<'a> { /// Offset at which we render headings. /// E.g. if `heading_offset: HeadingOffset::H2`, then `# something` renders an `

`. pub heading_offset: HeadingOffset, + /// `true` if the `custom_code_classes_in_docs` feature is enabled. + pub custom_code_classes_in_docs: bool, } /// A struct like `Markdown` that renders the markdown with a table of contents. pub(crate) struct MarkdownWithToc<'a> { @@ -103,6 +106,8 @@ pub(crate) struct MarkdownWithToc<'a> { pub(crate) error_codes: ErrorCodes, pub(crate) edition: Edition, pub(crate) playground: &'a Option, + /// `true` if the `custom_code_classes_in_docs` feature is enabled. + pub(crate) custom_code_classes_in_docs: bool, } /// A tuple struct like `Markdown` that renders the markdown escaping HTML tags /// and includes no paragraph tags. @@ -203,6 +208,7 @@ struct CodeBlocks<'p, 'a, I: Iterator>> { // Information about the playground if a URL has been specified, containing an // optional crate name and the URL. playground: &'p Option, + custom_code_classes_in_docs: bool, } impl<'p, 'a, I: Iterator>> CodeBlocks<'p, 'a, I> { @@ -211,8 +217,15 @@ impl<'p, 'a, I: Iterator>> CodeBlocks<'p, 'a, I> { error_codes: ErrorCodes, edition: Edition, playground: &'p Option, + custom_code_classes_in_docs: bool, ) -> Self { - CodeBlocks { inner: iter, check_error_codes: error_codes, edition, playground } + CodeBlocks { + inner: iter, + check_error_codes: error_codes, + edition, + playground, + custom_code_classes_in_docs, + } } } @@ -242,8 +255,12 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { let parse_result = match kind { CodeBlockKind::Fenced(ref lang) => { - let parse_result = - LangString::parse_without_check(lang, self.check_error_codes, false); + let parse_result = LangString::parse_without_check( + lang, + self.check_error_codes, + false, + self.custom_code_classes_in_docs, + ); if !parse_result.rust { let added_classes = parse_result.added_classes; let lang_string = if let Some(lang) = parse_result.unknown.first() { @@ -725,8 +742,17 @@ pub(crate) fn find_testable_code( error_codes: ErrorCodes, enable_per_target_ignores: bool, extra_info: Option<&ExtraInfo<'_>>, + custom_code_classes_in_docs: bool, ) { - find_codes(doc, tests, error_codes, enable_per_target_ignores, extra_info, false) + find_codes( + doc, + tests, + error_codes, + enable_per_target_ignores, + extra_info, + false, + custom_code_classes_in_docs, + ) } pub(crate) fn find_codes( @@ -736,6 +762,7 @@ pub(crate) fn find_codes( enable_per_target_ignores: bool, extra_info: Option<&ExtraInfo<'_>>, include_non_rust: bool, + custom_code_classes_in_docs: bool, ) { let mut parser = Parser::new(doc).into_offset_iter(); let mut prev_offset = 0; @@ -754,6 +781,7 @@ pub(crate) fn find_codes( error_codes, enable_per_target_ignores, extra_info, + custom_code_classes_in_docs, ) } } @@ -1153,8 +1181,15 @@ impl LangString { string: &str, allow_error_code_check: ErrorCodes, enable_per_target_ignores: bool, + custom_code_classes_in_docs: bool, ) -> Self { - Self::parse(string, allow_error_code_check, enable_per_target_ignores, None) + Self::parse( + string, + allow_error_code_check, + enable_per_target_ignores, + None, + custom_code_classes_in_docs, + ) } fn parse( @@ -1162,6 +1197,7 @@ impl LangString { allow_error_code_check: ErrorCodes, enable_per_target_ignores: bool, extra: Option<&ExtraInfo<'_>>, + custom_code_classes_in_docs: bool, ) -> Self { let allow_error_code_check = allow_error_code_check.as_bool(); let mut seen_rust_tags = false; @@ -1197,7 +1233,11 @@ impl LangString { seen_rust_tags = true; } LangStringToken::LangToken("custom") => { - seen_custom_tag = true; + if custom_code_classes_in_docs { + seen_custom_tag = true; + } else { + seen_other_tags = true; + } } LangStringToken::LangToken("test_harness") => { data.test_harness = true; @@ -1268,11 +1308,16 @@ impl LangString { data.unknown.push(x.to_owned()); } LangStringToken::KeyValueAttribute(key, value) => { - if key == "class" { - data.added_classes.push(value.to_owned()); - } else if let Some(extra) = extra { - extra - .error_invalid_codeblock_attr(format!("unsupported attribute `{key}`")); + if custom_code_classes_in_docs { + if key == "class" { + data.added_classes.push(value.to_owned()); + } else if let Some(extra) = extra { + extra.error_invalid_codeblock_attr(format!( + "unsupported attribute `{key}`" + )); + } + } else { + seen_other_tags = true; } } LangStringToken::ClassAttribute(class) => { @@ -1302,6 +1347,7 @@ impl Markdown<'_> { edition, playground, heading_offset, + custom_code_classes_in_docs, } = self; // This is actually common enough to special-case @@ -1324,7 +1370,7 @@ impl Markdown<'_> { let p = Footnotes::new(p); let p = LinkReplacer::new(p.map(|(ev, _)| ev), links); let p = TableWrapper::new(p); - let p = CodeBlocks::new(p, codes, edition, playground); + let p = CodeBlocks::new(p, codes, edition, playground, custom_code_classes_in_docs); html::push_html(&mut s, p); s @@ -1333,7 +1379,14 @@ impl Markdown<'_> { impl MarkdownWithToc<'_> { pub(crate) fn into_string(self) -> String { - let MarkdownWithToc { content: md, ids, error_codes: codes, edition, playground } = self; + let MarkdownWithToc { + content: md, + ids, + error_codes: codes, + edition, + playground, + custom_code_classes_in_docs, + } = self; let p = Parser::new_ext(md, main_body_opts()).into_offset_iter(); @@ -1345,7 +1398,7 @@ impl MarkdownWithToc<'_> { let p = HeadingLinks::new(p, Some(&mut toc), ids, HeadingOffset::H1); let p = Footnotes::new(p); let p = TableWrapper::new(p.map(|(ev, _)| ev)); - let p = CodeBlocks::new(p, codes, edition, playground); + let p = CodeBlocks::new(p, codes, edition, playground, custom_code_classes_in_docs); html::push_html(&mut s, p); } @@ -1786,7 +1839,11 @@ pub(crate) struct RustCodeBlock { /// Returns a range of bytes for each code block in the markdown that is tagged as `rust` or /// untagged (and assumed to be rust). -pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec { +pub(crate) fn rust_code_blocks( + md: &str, + extra_info: &ExtraInfo<'_>, + custom_code_classes_in_docs: bool, +) -> Vec { let mut code_blocks = vec![]; if md.is_empty() { @@ -1803,7 +1860,13 @@ pub(crate) fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_>) -> Vec) -> String { error_codes: shared.codes, edition: shared.edition(), playground: &shared.playground, - heading_offset: HeadingOffset::H1 + heading_offset: HeadingOffset::H1, + custom_code_classes_in_docs: false, } .into_string() ) @@ -437,6 +438,7 @@ fn render_markdown<'a, 'cx: 'a>( heading_offset: HeadingOffset, ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(move |f| { + let custom_code_classes_in_docs = cx.tcx().features().custom_code_classes_in_docs; write!( f, "
{}
", @@ -448,6 +450,7 @@ fn render_markdown<'a, 'cx: 'a>( edition: cx.shared.edition(), playground: &cx.shared.playground, heading_offset, + custom_code_classes_in_docs, } .into_string() ) @@ -1778,6 +1781,7 @@ fn render_impl( ", ); } + let custom_code_classes_in_docs = cx.tcx().features().custom_code_classes_in_docs; write!( w, "
{}
", @@ -1788,7 +1792,8 @@ fn render_impl( error_codes: cx.shared.codes, edition: cx.shared.edition(), playground: &cx.shared.playground, - heading_offset: HeadingOffset::H4 + heading_offset: HeadingOffset::H4, + custom_code_classes_in_docs, } .into_string() ); diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index 526eea30478ab..1ac3163b2cdbd 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -80,6 +80,8 @@ pub(crate) fn render>( error_codes, edition, playground: &playground, + // For markdown files, it'll be disabled until the feature is enabled by default. + custom_code_classes_in_docs: false, } .into_string() } else { @@ -91,6 +93,8 @@ pub(crate) fn render>( edition, playground: &playground, 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() }; @@ -154,7 +158,15 @@ pub(crate) fn test(options: Options) -> Result<(), String> { collector.set_position(DUMMY_SP); let codes = ErrorCodes::from(options.unstable_features.is_nightly_build()); - find_testable_code(&input_str, &mut collector, codes, options.enable_per_target_ignores, None); + // For markdown files, it'll be disabled until the feature is enabled by default. + find_testable_code( + &input_str, + &mut collector, + codes, + options.enable_per_target_ignores, + None, + false, + ); crate::doctest::run_tests(options.test_args, options.nocapture, collector.tests); Ok(()) diff --git a/src/librustdoc/passes/calculate_doc_coverage.rs b/src/librustdoc/passes/calculate_doc_coverage.rs index 592dd0a145cf5..60def40588aaa 100644 --- a/src/librustdoc/passes/calculate_doc_coverage.rs +++ b/src/librustdoc/passes/calculate_doc_coverage.rs @@ -208,7 +208,14 @@ impl<'a, 'b> DocVisitor for CoverageCalculator<'a, 'b> { let has_docs = !i.attrs.doc_strings.is_empty(); let mut tests = Tests { found_tests: 0 }; - find_testable_code(&i.doc_value(), &mut tests, ErrorCodes::No, false, None); + find_testable_code( + &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 hir_id = DocContext::as_local_hir_id(self.ctx.tcx, i.item_id).unwrap(); diff --git a/src/librustdoc/passes/check_custom_code_classes.rs b/src/librustdoc/passes/check_custom_code_classes.rs index 73e80372e4a41..1a703a4e96755 100644 --- a/src/librustdoc/passes/check_custom_code_classes.rs +++ b/src/librustdoc/passes/check_custom_code_classes.rs @@ -9,7 +9,9 @@ use crate::core::DocContext; use crate::fold::DocFolder; use crate::html::markdown::{find_codes, ErrorCodes, LangString}; -use rustc_session::parse::feature_err; +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 { @@ -55,23 +57,32 @@ pub(crate) fn look_for_custom_classes<'tcx>(cx: &DocContext<'tcx>, item: &Item) let mut tests = TestsWithCustomClasses { custom_classes_found: vec![] }; let dox = item.attrs.doc_value(); - find_codes(&dox, &mut tests, ErrorCodes::No, false, None, true); + find_codes(&dox, &mut tests, ErrorCodes::No, false, None, true, true); if !tests.custom_classes_found.is_empty() && !cx.tcx.features().custom_code_classes_in_docs { - feature_err( - &cx.tcx.sess.parse_sess, + let span = item.attr_span(cx.tcx); + let sess = &cx.tcx.sess.parse_sess; + let mut err = sess + .span_diagnostic + .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, - item.attr_span(cx.tcx), - "custom classes in code blocks are unstable", - ) - .note( + GateIssue::Language, + false, + ); + + 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=") ), - ) - .emit(); + ); + + // A later feature_err call can steal and cancel this warning. + err.stash(span, StashKey::EarlySyntaxWarning); } } diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index 96224d7c6e2e2..d1c4cc1f5952d 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -113,7 +113,14 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item let mut tests = Tests { found_tests: 0 }; - find_testable_code(dox, &mut tests, ErrorCodes::No, false, None); + find_testable_code( + 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 should_have_doc_example(cx, item) { diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs index 316b1a41c7da2..ac8a75a4f18c9 100644 --- a/src/librustdoc/passes/lint/check_code_block_syntax.rs +++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs @@ -20,7 +20,9 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &clean::Item) { if let Some(dox) = &item.opt_doc_value() { let sp = item.attr_span(cx.tcx); let extra = crate::html::markdown::ExtraInfo::new(cx.tcx, item.item_id.expect_def_id(), sp); - for code_block in markdown::rust_code_blocks(dox, &extra) { + for code_block in + markdown::rust_code_blocks(dox, &extra, cx.tcx.features().custom_code_classes_in_docs) + { check_rust_syntax(cx, item, dox, code_block); } } From c17abf124c5473d6cb24b3d1cc462bb3c71b55ae Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 17 Sep 2023 14:53:30 +0200 Subject: [PATCH 2/3] Update tests for `custom_code_classes_in_docs` feature --- src/librustdoc/html/markdown/tests.rs | 7 +++++-- .../rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs | 7 ++++++- .../feature-gate-custom_code_classes_in_docs.stderr | 7 +++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/html/markdown/tests.rs b/src/librustdoc/html/markdown/tests.rs index 7d89cb0c4e618..32957ac57fae9 100644 --- a/src/librustdoc/html/markdown/tests.rs +++ b/src/librustdoc/html/markdown/tests.rs @@ -49,7 +49,7 @@ fn test_unique_id() { fn test_lang_string_parse() { fn t(lg: LangString) { let s = &lg.original; - assert_eq!(LangString::parse(s, ErrorCodes::Yes, true, None), lg) + assert_eq!(LangString::parse(s, ErrorCodes::Yes, true, None, true), lg) } t(Default::default()); @@ -290,6 +290,7 @@ fn test_header() { edition: DEFAULT_EDITION, playground: &None, heading_offset: HeadingOffset::H2, + custom_code_classes_in_docs: true, } .into_string(); assert_eq!(output, expect, "original: {}", input); @@ -329,6 +330,7 @@ fn test_header_ids_multiple_blocks() { edition: DEFAULT_EDITION, playground: &None, heading_offset: HeadingOffset::H2, + custom_code_classes_in_docs: true, } .into_string(); assert_eq!(output, expect, "original: {}", input); @@ -433,7 +435,7 @@ fn test_find_testable_code_line() { } } let mut lines = Vec::::new(); - find_testable_code(input, &mut lines, ErrorCodes::No, false, None); + find_testable_code(input, &mut lines, ErrorCodes::No, false, None, true); assert_eq!(lines, expect); } @@ -458,6 +460,7 @@ fn test_ascii_with_prepending_hashtag() { edition: DEFAULT_EDITION, playground: &None, heading_offset: HeadingOffset::H2, + custom_code_classes_in_docs: true, } .into_string(); assert_eq!(output, expect, "original: {}", input); 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 index 8aa13b2d5d10c..3f0f8b5b9f997 100644 --- a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs +++ b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.rs @@ -1,5 +1,10 @@ +// check-pass + /// ```{class=language-c} /// int main(void) { return 0; } /// ``` -//~^^^ ERROR 1:1: 3:8: custom classes in code blocks are unstable [E0658] +//~^^^ WARNING custom classes in code blocks will change behaviour +//~| NOTE found these custom classes: class=language-c +//~| NOTE see issue #79483 +//~| HELP add `#![feature(custom_code_classes_in_docs)]` to the crate attributes to enable 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 index c41ebfc807334..1a2360d9b3004 100644 --- a/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr +++ b/tests/rustdoc-ui/feature-gate-custom_code_classes_in_docs.stderr @@ -1,5 +1,5 @@ -error[E0658]: custom classes in code blocks are unstable - --> $DIR/feature-gate-custom_code_classes_in_docs.rs:1:1 +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; } @@ -10,6 +10,5 @@ LL | | /// ``` = 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 +warning: 1 warning emitted -For more information about this error, try `rustc --explain E0658`. From e1e1a02f529a1797d77db627882321edf038f78c Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Sun, 17 Sep 2023 18:43:54 +0000 Subject: [PATCH 3/3] Update src/librustdoc/markdown.rs Co-authored-by: Michael Howell --- src/librustdoc/markdown.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs index 1ac3163b2cdbd..b74a9392f270e 100644 --- a/src/librustdoc/markdown.rs +++ b/src/librustdoc/markdown.rs @@ -158,7 +158,7 @@ pub(crate) fn test(options: Options) -> Result<(), String> { collector.set_position(DUMMY_SP); let codes = ErrorCodes::from(options.unstable_features.is_nightly_build()); - // For markdown files, it'll 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( &input_str, &mut collector,