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::{ErrorCodes, LangString, MdRelLine, find_testable_code};
17
18struct MdCollector {
19 tests: Vec<ScrapedDocTest>,
20 cur_path: Vec<String>,
21 filename: FileName,
22}
23
24impl DocTestVisitor for MdCollector {
25 fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) {
26 let filename = self.filename.clone();
27 let line = 1 + rel_line.offset();
29 self.tests.push(ScrapedDocTest::new(
30 filename,
31 line,
32 self.cur_path.clone(),
33 config,
34 test,
35 DUMMY_SP,
36 Vec::new(),
37 ));
38 }
39
40 fn visit_header(&mut self, name: &str, level: u32) {
41 let name = name
44 .chars()
45 .enumerate()
46 .map(|(i, c)| {
47 if (i == 0 && rustc_lexer::is_id_start(c))
48 || (i != 0 && rustc_lexer::is_id_continue(c))
49 {
50 c
51 } else {
52 '_'
53 }
54 })
55 .collect::<String>();
56
57 let level = level as usize;
62 if level <= self.cur_path.len() {
63 self.cur_path.truncate(level);
68 self.cur_path[level - 1] = name;
69 } else {
70 if level - 1 > self.cur_path.len() {
75 self.cur_path.resize(level - 1, "_".to_owned());
76 }
77 self.cur_path.push(name);
78 }
79 }
80}
81
82pub(crate) fn test(input: &Input, options: Options, dcx: DiagCtxtHandle<'_>) -> Result<(), String> {
84 let input_str = match input {
85 Input::File(path) => {
86 read_to_string(path).map_err(|err| format!("{}: {err}", path.display()))?
87 }
88 Input::Str { name: _, input } => input.clone(),
89 };
90
91 let crate_name = input.filestem().to_string();
93 let temp_dir =
94 tempdir().map_err(|error| format!("failed to create temporary directory: {error:?}"))?;
95 let args_file = temp_dir.path().join("rustdoc-cfgs");
96 generate_args_file(&args_file, &options)?;
97
98 let opts = GlobalTestOptions {
99 crate_name,
100 no_crate_inject: true,
101 insert_indent_space: false,
102 args_file,
103 };
104
105 let mut md_collector = MdCollector {
106 tests: vec![],
107 cur_path: vec![],
108 filename: input
109 .opt_path()
110 .map(|f| {
111 let file_mapping = FilePathMapping::empty();
114 FileName::Real(file_mapping.to_real_filename(&RealFileName::empty(), f))
115 })
116 .unwrap_or(FileName::Custom("input".to_owned())),
117 };
118 let codes = ErrorCodes::from(options.unstable_features.is_nightly_build());
119
120 find_testable_code(&input_str, &mut md_collector, codes, None);
121
122 let mut collector = CreateRunnableDocTests::new(options.clone(), opts);
123 md_collector.tests.into_iter().for_each(|t| collector.add_test(t, None));
124 let CreateRunnableDocTests { opts, rustdoc_options, standalone_tests, mergeable_tests, .. } =
125 collector;
126 crate::doctest::run_tests(
127 dcx,
128 opts,
129 &rustdoc_options,
130 &Arc::new(Mutex::new(Vec::new())),
131 standalone_tests,
132 mergeable_tests,
133 None,
134 );
135 Ok(())
136}