Skip to content

Commit 0959802

Browse files
authored
Rollup merge of #71408 - GuillaumeGomez:check-code-blocks-tags, r=kinnison
Check code blocks tags Fixes #71347. Explanations here: I realized recently that it was a common issue to confuse/misspell tags on code blocks. This is actually quite a big issue since it generally ends up in a code blocks being ignored since it's not being considered as a rust one. With this new warning, users will at least be notified about it. PS: some improvements can be done on the error rendering but considering how big the PR already is, I think it's better to do it afterwards. r? @ollie27 cc @rust-lang/rustdoc
2 parents 2a1cd44 + 977603c commit 0959802

File tree

13 files changed

+711
-27
lines changed

13 files changed

+711
-27
lines changed

src/librustc_lint/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ use rustc_middle::ty::query::Providers;
6161
use rustc_middle::ty::TyCtxt;
6262
use rustc_session::lint::builtin::{
6363
BARE_TRAIT_OBJECTS, ELIDED_LIFETIMES_IN_PATHS, EXPLICIT_OUTLIVES_REQUIREMENTS,
64-
INTRA_DOC_LINK_RESOLUTION_FAILURE, MISSING_DOC_CODE_EXAMPLES, PRIVATE_DOC_TESTS,
64+
INTRA_DOC_LINK_RESOLUTION_FAILURE, INVALID_CODEBLOCK_ATTRIBUTE, MISSING_DOC_CODE_EXAMPLES,
65+
PRIVATE_DOC_TESTS,
6566
};
6667
use rustc_span::Span;
6768

@@ -299,6 +300,7 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
299300
add_lint_group!(
300301
"rustdoc",
301302
INTRA_DOC_LINK_RESOLUTION_FAILURE,
303+
INVALID_CODEBLOCK_ATTRIBUTE,
302304
MISSING_DOC_CODE_EXAMPLES,
303305
PRIVATE_DOC_TESTS
304306
);

src/librustc_session/lint/builtin.rs

+7
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,12 @@ declare_lint! {
386386
"failures in resolving intra-doc link targets"
387387
}
388388

389+
declare_lint! {
390+
pub INVALID_CODEBLOCK_ATTRIBUTE,
391+
Warn,
392+
"codeblock attribute looks a lot like a known one"
393+
}
394+
389395
declare_lint! {
390396
pub MISSING_CRATE_LEVEL_DOCS,
391397
Allow,
@@ -553,6 +559,7 @@ declare_lint_pass! {
553559
UNSTABLE_NAME_COLLISIONS,
554560
IRREFUTABLE_LET_PATTERNS,
555561
INTRA_DOC_LINK_RESOLUTION_FAILURE,
562+
INVALID_CODEBLOCK_ATTRIBUTE,
556563
MISSING_CRATE_LEVEL_DOCS,
557564
MISSING_DOC_CODE_EXAMPLES,
558565
PRIVATE_DOC_TESTS,

src/librustdoc/core.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
253253
let missing_doc_example = rustc_lint::builtin::MISSING_DOC_CODE_EXAMPLES.name;
254254
let private_doc_tests = rustc_lint::builtin::PRIVATE_DOC_TESTS.name;
255255
let no_crate_level_docs = rustc_lint::builtin::MISSING_CRATE_LEVEL_DOCS.name;
256+
let invalid_codeblock_attribute_name = rustc_lint::builtin::INVALID_CODEBLOCK_ATTRIBUTE.name;
256257

257258
// In addition to those specific lints, we also need to whitelist those given through
258259
// command line, otherwise they'll get ignored and we don't want that.
@@ -263,6 +264,7 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
263264
missing_doc_example.to_owned(),
264265
private_doc_tests.to_owned(),
265266
no_crate_level_docs.to_owned(),
267+
invalid_codeblock_attribute_name.to_owned(),
266268
];
267269

268270
whitelisted_lints.extend(lint_opts.iter().map(|(lint, _)| lint).cloned());
@@ -275,7 +277,10 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
275277

276278
let lint_opts = lints()
277279
.filter_map(|lint| {
278-
if lint.name == warnings_lint_name || lint.name == intra_link_resolution_failure_name {
280+
if lint.name == warnings_lint_name
281+
|| lint.name == intra_link_resolution_failure_name
282+
|| lint.name == invalid_codeblock_attribute_name
283+
{
279284
None
280285
} else {
281286
Some((lint.name_lower(), lint::Allow))

src/librustdoc/html/markdown.rs

+114-4
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@
2020
#![allow(non_camel_case_types)]
2121

2222
use rustc_data_structures::fx::FxHashMap;
23+
use rustc_hir::def_id::DefId;
24+
use rustc_hir::HirId;
25+
use rustc_middle::ty::TyCtxt;
26+
use rustc_session::lint;
2327
use rustc_span::edition::Edition;
28+
use rustc_span::Span;
2429
use std::borrow::Cow;
2530
use std::cell::RefCell;
2631
use std::collections::VecDeque;
@@ -192,7 +197,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
192197
if let Some(Event::Start(Tag::CodeBlock(kind))) = event {
193198
let parse_result = match kind {
194199
CodeBlockKind::Fenced(ref lang) => {
195-
LangString::parse(&lang, self.check_error_codes, false)
200+
LangString::parse_without_check(&lang, self.check_error_codes, false)
196201
}
197202
CodeBlockKind::Indented => LangString::all_false(),
198203
};
@@ -560,6 +565,7 @@ pub fn find_testable_code<T: test::Tester>(
560565
tests: &mut T,
561566
error_codes: ErrorCodes,
562567
enable_per_target_ignores: bool,
568+
extra_info: Option<&ExtraInfo<'_, '_>>,
563569
) {
564570
let mut parser = Parser::new(doc).into_offset_iter();
565571
let mut prev_offset = 0;
@@ -573,7 +579,12 @@ pub fn find_testable_code<T: test::Tester>(
573579
if lang.is_empty() {
574580
LangString::all_false()
575581
} else {
576-
LangString::parse(lang, error_codes, enable_per_target_ignores)
582+
LangString::parse(
583+
lang,
584+
error_codes,
585+
enable_per_target_ignores,
586+
extra_info,
587+
)
577588
}
578589
}
579590
CodeBlockKind::Indented => LangString::all_false(),
@@ -615,6 +626,49 @@ pub fn find_testable_code<T: test::Tester>(
615626
}
616627
}
617628

629+
pub struct ExtraInfo<'a, 'b> {
630+
hir_id: Option<HirId>,
631+
item_did: Option<DefId>,
632+
sp: Span,
633+
tcx: &'a TyCtxt<'b>,
634+
}
635+
636+
impl<'a, 'b> ExtraInfo<'a, 'b> {
637+
pub fn new(tcx: &'a TyCtxt<'b>, hir_id: HirId, sp: Span) -> ExtraInfo<'a, 'b> {
638+
ExtraInfo { hir_id: Some(hir_id), item_did: None, sp, tcx }
639+
}
640+
641+
pub fn new_did(tcx: &'a TyCtxt<'b>, did: DefId, sp: Span) -> ExtraInfo<'a, 'b> {
642+
ExtraInfo { hir_id: None, item_did: Some(did), sp, tcx }
643+
}
644+
645+
fn error_invalid_codeblock_attr(&self, msg: &str, help: &str) {
646+
let hir_id = match (self.hir_id, self.item_did) {
647+
(Some(h), _) => h,
648+
(None, Some(item_did)) => {
649+
match self.tcx.hir().as_local_hir_id(item_did) {
650+
Some(hir_id) => hir_id,
651+
None => {
652+
// If non-local, no need to check anything.
653+
return;
654+
}
655+
}
656+
}
657+
(None, None) => return,
658+
};
659+
self.tcx.struct_span_lint_hir(
660+
lint::builtin::INVALID_CODEBLOCK_ATTRIBUTE,
661+
hir_id,
662+
self.sp,
663+
|lint| {
664+
let mut diag = lint.build(msg);
665+
diag.help(help);
666+
diag.emit();
667+
},
668+
);
669+
}
670+
}
671+
618672
#[derive(Eq, PartialEq, Clone, Debug)]
619673
pub struct LangString {
620674
original: String,
@@ -652,10 +706,19 @@ impl LangString {
652706
}
653707
}
654708

709+
fn parse_without_check(
710+
string: &str,
711+
allow_error_code_check: ErrorCodes,
712+
enable_per_target_ignores: bool,
713+
) -> LangString {
714+
Self::parse(string, allow_error_code_check, enable_per_target_ignores, None)
715+
}
716+
655717
fn parse(
656718
string: &str,
657719
allow_error_code_check: ErrorCodes,
658720
enable_per_target_ignores: bool,
721+
extra: Option<&ExtraInfo<'_, '_>>,
659722
) -> LangString {
660723
let allow_error_code_check = allow_error_code_check.as_bool();
661724
let mut seen_rust_tags = false;
@@ -715,6 +778,53 @@ impl LangString {
715778
seen_other_tags = true;
716779
}
717780
}
781+
x if extra.is_some() => {
782+
let s = x.to_lowercase();
783+
match if s == "compile-fail" || s == "compile_fail" || s == "compilefail" {
784+
Some((
785+
"compile_fail",
786+
"the code block will either not be tested if not marked as a rust one \
787+
or won't fail if it compiles successfully",
788+
))
789+
} else if s == "should-panic" || s == "should_panic" || s == "shouldpanic" {
790+
Some((
791+
"should_panic",
792+
"the code block will either not be tested if not marked as a rust one \
793+
or won't fail if it doesn't panic when running",
794+
))
795+
} else if s == "no-run" || s == "no_run" || s == "norun" {
796+
Some((
797+
"no_run",
798+
"the code block will either not be tested if not marked as a rust one \
799+
or will be run (which you might not want)",
800+
))
801+
} else if s == "allow-fail" || s == "allow_fail" || s == "allowfail" {
802+
Some((
803+
"allow_fail",
804+
"the code block will either not be tested if not marked as a rust one \
805+
or will be run (which you might not want)",
806+
))
807+
} else if s == "test-harness" || s == "test_harness" || s == "testharness" {
808+
Some((
809+
"test_harness",
810+
"the code block will either not be tested if not marked as a rust one \
811+
or the code will be wrapped inside a main function",
812+
))
813+
} else {
814+
None
815+
} {
816+
Some((flag, help)) => {
817+
if let Some(ref extra) = extra {
818+
extra.error_invalid_codeblock_attr(
819+
&format!("unknown attribute `{}`. Did you mean `{}`?", x, flag),
820+
help,
821+
);
822+
}
823+
}
824+
None => {}
825+
}
826+
seen_other_tags = true;
827+
}
718828
_ => seen_other_tags = true,
719829
}
720830
}
@@ -934,7 +1044,7 @@ crate struct RustCodeBlock {
9341044

9351045
/// Returns a range of bytes for each code block in the markdown that is tagged as `rust` or
9361046
/// untagged (and assumed to be rust).
937-
crate fn rust_code_blocks(md: &str) -> Vec<RustCodeBlock> {
1047+
crate fn rust_code_blocks(md: &str, extra_info: &ExtraInfo<'_, '_>) -> Vec<RustCodeBlock> {
9381048
let mut code_blocks = vec![];
9391049

9401050
if md.is_empty() {
@@ -951,7 +1061,7 @@ crate fn rust_code_blocks(md: &str) -> Vec<RustCodeBlock> {
9511061
let lang_string = if syntax.is_empty() {
9521062
LangString::all_false()
9531063
} else {
954-
LangString::parse(&*syntax, ErrorCodes::Yes, false)
1064+
LangString::parse(&*syntax, ErrorCodes::Yes, false, Some(extra_info))
9551065
};
9561066
if !lang_string.rust {
9571067
continue;

src/librustdoc/html/markdown/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ fn test_lang_string_parse() {
6464
edition: Option<Edition>,
6565
) {
6666
assert_eq!(
67-
LangString::parse(s, ErrorCodes::Yes, true),
67+
LangString::parse(s, ErrorCodes::Yes, true, None),
6868
LangString {
6969
should_panic,
7070
no_run,

src/librustdoc/markdown.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ pub fn test(mut options: Options, diag: &rustc_errors::Handler) -> i32 {
153153
collector.set_position(DUMMY_SP);
154154
let codes = ErrorCodes::from(UnstableFeatures::from_environment().is_nightly_build());
155155

156-
find_testable_code(&input_str, &mut collector, codes, options.enable_per_target_ignores);
156+
find_testable_code(&input_str, &mut collector, codes, options.enable_per_target_ignores, None);
157157

158158
options.test_args.insert(0, "rustdoctest".to_string());
159159
testing::test_main(

src/librustdoc/passes/check_code_block_syntax.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::clean;
1010
use crate::core::DocContext;
1111
use crate::fold::DocFolder;
1212
use crate::html::markdown::{self, RustCodeBlock};
13-
use crate::passes::Pass;
13+
use crate::passes::{span_of_attrs, Pass};
1414

1515
pub const CHECK_CODE_BLOCK_SYNTAX: Pass = Pass {
1616
name: "check-code-block-syntax",
@@ -114,7 +114,9 @@ impl<'a, 'tcx> SyntaxChecker<'a, 'tcx> {
114114
impl<'a, 'tcx> DocFolder for SyntaxChecker<'a, 'tcx> {
115115
fn fold_item(&mut self, item: clean::Item) -> Option<clean::Item> {
116116
if let Some(dox) = &item.attrs.collapsed_doc_value() {
117-
for code_block in markdown::rust_code_blocks(&dox) {
117+
let sp = span_of_attrs(&item.attrs).unwrap_or(item.source.span());
118+
let extra = crate::html::markdown::ExtraInfo::new_did(&self.cx.tcx, item.def_id, sp);
119+
for code_block in markdown::rust_code_blocks(&dox, &extra) {
118120
self.check_rust_syntax(&item, &dox, code_block);
119121
}
120122
}

src/librustdoc/passes/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ pub fn look_for_tests<'tcx>(
338338

339339
let mut tests = Tests { found_tests: 0 };
340340

341-
find_testable_code(&dox, &mut tests, ErrorCodes::No, false);
341+
find_testable_code(&dox, &mut tests, ErrorCodes::No, false, None);
342342

343343
if check_missing_code && tests.found_tests == 0 {
344344
let sp = span_of_attrs(&item.attrs).unwrap_or(item.source.span());

0 commit comments

Comments
 (0)