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