Skip to main content

rustdoc/doctest/
extracted.rs

1//! Rustdoc's doctest extraction.
2//!
3//! This module contains the logic to extract doctests and output a JSON containing this
4//! information.
5
6use rustc_span::RemapPathScopeComponents;
7use rustc_span::edition::Edition;
8use serde::Serialize;
9
10use super::make::DocTestWrapResult;
11use super::{BuildDocTestBuilder, ScrapedDocTest};
12use crate::config::Options as RustdocOptions;
13use crate::html::markdown;
14
15/// The version of JSON output that this code generates.
16///
17/// This integer is incremented with every breaking change to the API,
18/// and is returned along with the JSON blob into the `format_version` root field.
19/// Consuming code should assert that this value matches the format version(s) that it supports.
20const FORMAT_VERSION: u32 = 2;
21
22#[derive(Serialize)]
23pub(crate) struct ExtractedDocTests {
24    format_version: u32,
25    doctests: Vec<ExtractedDocTest>,
26}
27
28impl ExtractedDocTests {
29    pub(crate) fn new() -> Self {
30        Self { format_version: FORMAT_VERSION, doctests: Vec::new() }
31    }
32
33    pub(crate) fn add_test(
34        &mut self,
35        scraped_test: ScrapedDocTest,
36        opts: &super::GlobalTestOptions,
37        options: &RustdocOptions,
38    ) {
39        let edition = scraped_test.edition(options);
40        self.add_test_with_edition(scraped_test, opts, edition)
41    }
42
43    /// This method is used by unit tests to not have to provide a `RustdocOptions`.
44    pub(crate) fn add_test_with_edition(
45        &mut self,
46        scraped_test: ScrapedDocTest,
47        opts: &super::GlobalTestOptions,
48        edition: Edition,
49    ) {
50        let ScrapedDocTest { filename, line, langstr, text, name, global_crate_attrs, .. } =
51            scraped_test;
52
53        let doctest = BuildDocTestBuilder::new(&text)
54            .crate_name(&opts.crate_name)
55            .global_crate_attrs(global_crate_attrs)
56            .edition(edition)
57            .lang_str(&langstr)
58            .build(None);
59        let (wrapped, _size) = doctest.generate_unique_doctest(
60            &text,
61            langstr.test_harness,
62            opts,
63            Some(&opts.crate_name),
64        );
65        self.doctests.push(ExtractedDocTest {
66            file: filename.display(RemapPathScopeComponents::DOCUMENTATION).to_string(),
67            line,
68            doctest_attributes: langstr.into(),
69            doctest_code: match wrapped {
70                DocTestWrapResult::Valid { crate_level_code, wrapper, code } => Some(DocTest {
71                    crate_level: crate_level_code,
72                    code,
73                    wrapper: wrapper.map(
74                        |super::make::WrapperInfo { before, after, returns_result, .. }| {
75                            WrapperInfo { before, after, returns_result }
76                        },
77                    ),
78                }),
79                DocTestWrapResult::SyntaxError { .. } => None,
80            },
81            original_code: text,
82            name,
83        });
84    }
85
86    #[cfg(test)]
87    pub(crate) fn doctests(&self) -> &[ExtractedDocTest] {
88        &self.doctests
89    }
90}
91
92#[derive(Serialize)]
93pub(crate) struct WrapperInfo {
94    before: String,
95    after: String,
96    returns_result: bool,
97}
98
99#[derive(Serialize)]
100pub(crate) struct DocTest {
101    crate_level: String,
102    code: String,
103    /// This field can be `None` if one of the following conditions is true:
104    ///
105    /// * The doctest's codeblock has the `test_harness` attribute.
106    /// * The doctest has a `main` function.
107    /// * The doctest has the `![no_std]` attribute.
108    pub(crate) wrapper: Option<WrapperInfo>,
109}
110
111#[derive(Serialize)]
112pub(crate) struct ExtractedDocTest {
113    file: String,
114    line: usize,
115    doctest_attributes: LangString,
116    original_code: String,
117    /// `None` if the code syntax is invalid.
118    pub(crate) doctest_code: Option<DocTest>,
119    name: String,
120}
121
122#[derive(Serialize)]
123pub(crate) enum Ignore {
124    All,
125    None,
126    Some(Vec<String>),
127}
128
129impl From<markdown::Ignore> for Ignore {
130    fn from(original: markdown::Ignore) -> Self {
131        match original {
132            markdown::Ignore::All => Self::All,
133            markdown::Ignore::None => Self::None,
134            markdown::Ignore::Some(values) => Self::Some(values),
135        }
136    }
137}
138
139#[derive(Serialize)]
140struct LangString {
141    pub(crate) original: String,
142    pub(crate) should_panic: bool,
143    pub(crate) no_run: bool,
144    pub(crate) ignore: Ignore,
145    pub(crate) rust: bool,
146    pub(crate) test_harness: bool,
147    pub(crate) compile_fail: bool,
148    pub(crate) standalone_crate: bool,
149    pub(crate) error_codes: Vec<String>,
150    pub(crate) edition: Option<String>,
151    pub(crate) added_css_classes: Vec<String>,
152    pub(crate) unknown: Vec<String>,
153}
154
155impl From<markdown::LangString> for LangString {
156    fn from(original: markdown::LangString) -> Self {
157        let markdown::LangString {
158            original,
159            should_panic,
160            no_run,
161            ignore,
162            rust,
163            test_harness,
164            compile_fail,
165            standalone_crate,
166            error_codes,
167            edition,
168            added_classes,
169            unknown,
170        } = original;
171
172        Self {
173            original,
174            should_panic,
175            no_run,
176            ignore: ignore.into(),
177            rust,
178            test_harness,
179            compile_fail,
180            standalone_crate,
181            error_codes,
182            edition: edition.map(|edition| edition.to_string()),
183            added_css_classes: added_classes,
184            unknown,
185        }
186    }
187}