rustdoc/doctest/
markdown.rs1use std::fs::read_to_string;
4use std::sync::{Arc, Mutex};
5
6use rustc_errors::DiagCtxtHandle;
7use rustc_session::config::Input;
8use rustc_span::source_map::FilePathMapping;
9use rustc_span::{DUMMY_SP, FileName, RealFileName};
10use tempfile::tempdir;
11
12use super::{
13 CreateRunnableDocTests, DocTestVisitor, GlobalTestOptions, ScrapedDocTest, generate_args_file,
14};
15use crate::config::Options;
16use crate::html::markdown::{
17 CodeLineMapping, ErrorCodes, LangString, MdRelLine, find_testable_code,
18};
19
20struct MdCollector {
21 tests: Vec<ScrapedDocTest>,
22 cur_path: Vec<String>,
23 filename: FileName,
24}
25
26impl DocTestVisitor for MdCollector {
27 fn visit_test(
28 &mut self,
29 test: String,
30 config: LangString,
31 rel_line: MdRelLine,
32 code_mappings: Vec<CodeLineMapping>,
33 ) {
34 let filename = self.filename.clone();
35 let line = 1 + rel_line.offset();
37 self.tests.push(ScrapedDocTest::new(
38 filename,
39 line,
40 self.cur_path.clone(),
41 config,
42 test,
43 DUMMY_SP,
44 code_mappings,
45 Vec::new(),
46 ));
47 }
48
49 fn visit_header(&mut self, name: &str, level: u32) {
50 let name = name
53 .chars()
54 .enumerate()
55 .map(|(i, c)| {
56 if (i == 0 && rustc_lexer::is_id_start(c))
57 || (i != 0 && rustc_lexer::is_id_continue(c))
58 {
59 c
60 } else {
61 '_'
62 }
63 })
64 .collect::<String>();
65
66 let level = level as usize;
71 if level <= self.cur_path.len() {
72 self.cur_path.truncate(level);
77 self.cur_path[level - 1] = name;
78 } else {
79 if level - 1 > self.cur_path.len() {
84 self.cur_path.resize(level - 1, "_".to_owned());
85 }
86 self.cur_path.push(name);
87 }
88 }
89}
90
91pub(crate) fn test(input: &Input, options: Options, dcx: DiagCtxtHandle<'_>) -> Result<(), String> {
93 let input_str = match input {
94 Input::File(path) => {
95 read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))?
96 }
97 Input::Str { name: _, input } => input.clone(),
98 };
99
100 let crate_name = input.filestem().to_string();
102 let temp_dir =
103 tempdir().map_err(|error| format!("failed to create temporary directory: {error:?}"))?;
104 let args_file = temp_dir.path().join("rustdoc-cfgs");
105 generate_args_file(&args_file, &options)?;
106
107 let opts = GlobalTestOptions {
108 crate_name,
109 no_crate_inject: true,
110 insert_indent_space: false,
111 args_file,
112 };
113
114 let mut md_collector = MdCollector {
115 tests: vec![],
116 cur_path: vec![],
117 filename: input
118 .opt_path()
119 .map(|f| {
120 let file_mapping = FilePathMapping::empty();
123 FileName::Real(file_mapping.to_real_filename(&RealFileName::empty(), f))
124 })
125 .unwrap_or(FileName::Custom("input".to_owned())),
126 };
127 let codes = ErrorCodes::from(options.unstable_features.is_nightly_build());
128
129 find_testable_code(&input_str, &mut md_collector, codes, None);
130
131 let mut collector = CreateRunnableDocTests::new(options.clone(), opts);
132 md_collector.tests.into_iter().for_each(|t| collector.add_test(t, None));
133 let CreateRunnableDocTests { opts, rustdoc_options, standalone_tests, mergeable_tests, .. } =
134 collector;
135 crate::doctest::run_tests(
136 dcx,
137 opts,
138 &rustdoc_options,
139 &Arc::new(Mutex::new(Vec::new())),
140 standalone_tests,
141 mergeable_tests,
142 None,
143 );
144 Ok(())
145}