diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md
index 69e5a5adbec29..27910ad0ab796 100644
--- a/src/doc/rustdoc/src/unstable-features.md
+++ b/src/doc/rustdoc/src/unstable-features.md
@@ -581,7 +581,9 @@ For this rust code:
 
 ```rust
 /// ```
+/// #![allow(dead_code)]
 /// let x = 12;
+/// Ok(())
 /// ```
 pub trait Trait {}
 ```
@@ -590,10 +592,10 @@ The generated output (formatted) will look like this:
 
 ```json
 {
-  "format_version": 1,
+  "format_version": 2,
   "doctests": [
     {
-      "file": "foo.rs",
+      "file": "src/lib.rs",
       "line": 1,
       "doctest_attributes": {
         "original": "",
@@ -609,9 +611,17 @@ The generated output (formatted) will look like this:
         "added_css_classes": [],
         "unknown": []
       },
-      "original_code": "let x = 12;",
-      "doctest_code": "#![allow(unused)]\nfn main() {\nlet x = 12;\n}",
-      "name": "foo.rs - Trait (line 1)"
+      "original_code": "#![allow(dead_code)]\nlet x = 12;\nOk(())",
+      "doctest_code": {
+        "crate_level": "#![allow(unused)]\n#![allow(dead_code)]\n\n",
+        "code": "let x = 12;\nOk(())",
+        "wrapper": {
+          "before": "fn main() { fn _inner() -> core::result::Result<(), impl core::fmt::Debug> {\n",
+          "after": "\n} _inner().unwrap() }",
+          "returns_result": true
+        }
+      },
+      "name": "src/lib.rs - (line 1)"
     }
   ]
 }
@@ -624,6 +634,10 @@ The generated output (formatted) will look like this:
    * `doctest_attributes` contains computed information about the attributes used on the doctests. For more information about doctest attributes, take a look [here](write-documentation/documentation-tests.html#attributes).
    * `original_code` is the code as written in the source code before rustdoc modifies it.
    * `doctest_code` is the code modified by rustdoc that will be run. If there is a fatal syntax error, this field will not be present.
+     * `crate_level` is the crate level code (like attributes or `extern crate`) that will be added at the top-level of the generated doctest.
+     * `code` is "naked" doctest without anything from `crate_level` and `wrapper` content.
+     * `wrapper` contains extra code that will be added before and after `code`.
+       * `returns_result` is a boolean. If `true`, it means that the doctest returns a `Result` type.
    * `name` is the name generated by rustdoc which represents this doctest.
 
 ### html
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index a81d6020f7143..130fdff1afe29 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -1053,14 +1053,14 @@ fn doctest_run_fn(
     let report_unused_externs = |uext| {
         unused_externs.lock().unwrap().push(uext);
     };
-    let (full_test_code, full_test_line_offset) = doctest.generate_unique_doctest(
+    let (wrapped, full_test_line_offset) = doctest.generate_unique_doctest(
         &scraped_test.text,
         scraped_test.langstr.test_harness,
         &global_opts,
         Some(&global_opts.crate_name),
     );
     let runnable_test = RunnableDocTest {
-        full_test_code,
+        full_test_code: wrapped.to_string(),
         full_test_line_offset,
         test_opts,
         global_opts,
diff --git a/src/librustdoc/doctest/extracted.rs b/src/librustdoc/doctest/extracted.rs
index ebe6bfd22ba10..925fb6fee2caa 100644
--- a/src/librustdoc/doctest/extracted.rs
+++ b/src/librustdoc/doctest/extracted.rs
@@ -3,8 +3,10 @@
 //! This module contains the logic to extract doctests and output a JSON containing this
 //! information.
 
+use rustc_span::edition::Edition;
 use serde::Serialize;
 
+use super::make::DocTestWrapResult;
 use super::{BuildDocTestBuilder, ScrapedDocTest};
 use crate::config::Options as RustdocOptions;
 use crate::html::markdown;
@@ -14,7 +16,7 @@ use crate::html::markdown;
 /// This integer is incremented with every breaking change to the API,
 /// and is returned along with the JSON blob into the `format_version` root field.
 /// Consuming code should assert that this value matches the format version(s) that it supports.
-const FORMAT_VERSION: u32 = 1;
+const FORMAT_VERSION: u32 = 2;
 
 #[derive(Serialize)]
 pub(crate) struct ExtractedDocTests {
@@ -34,7 +36,16 @@ impl ExtractedDocTests {
         options: &RustdocOptions,
     ) {
         let edition = scraped_test.edition(options);
+        self.add_test_with_edition(scraped_test, opts, edition)
+    }
 
+    /// This method is used by unit tests to not have to provide a `RustdocOptions`.
+    pub(crate) fn add_test_with_edition(
+        &mut self,
+        scraped_test: ScrapedDocTest,
+        opts: &super::GlobalTestOptions,
+        edition: Edition,
+    ) {
         let ScrapedDocTest { filename, line, langstr, text, name, global_crate_attrs, .. } =
             scraped_test;
 
@@ -44,8 +55,7 @@ impl ExtractedDocTests {
             .edition(edition)
             .lang_str(&langstr)
             .build(None);
-
-        let (full_test_code, size) = doctest.generate_unique_doctest(
+        let (wrapped, _size) = doctest.generate_unique_doctest(
             &text,
             langstr.test_harness,
             opts,
@@ -55,11 +65,46 @@ impl ExtractedDocTests {
             file: filename.prefer_remapped_unconditionaly().to_string(),
             line,
             doctest_attributes: langstr.into(),
-            doctest_code: if size != 0 { Some(full_test_code) } else { None },
+            doctest_code: match wrapped {
+                DocTestWrapResult::Valid { crate_level_code, wrapper, code } => Some(DocTest {
+                    crate_level: crate_level_code,
+                    code,
+                    wrapper: wrapper.map(
+                        |super::make::WrapperInfo { before, after, returns_result, .. }| {
+                            WrapperInfo { before, after, returns_result }
+                        },
+                    ),
+                }),
+                DocTestWrapResult::SyntaxError { .. } => None,
+            },
             original_code: text,
             name,
         });
     }
+
+    #[cfg(test)]
+    pub(crate) fn doctests(&self) -> &[ExtractedDocTest] {
+        &self.doctests
+    }
+}
+
+#[derive(Serialize)]
+pub(crate) struct WrapperInfo {
+    before: String,
+    after: String,
+    returns_result: bool,
+}
+
+#[derive(Serialize)]
+pub(crate) struct DocTest {
+    crate_level: String,
+    code: String,
+    /// This field can be `None` if one of the following conditions is true:
+    ///
+    /// * The doctest's codeblock has the `test_harness` attribute.
+    /// * The doctest has a `main` function.
+    /// * The doctest has the `![no_std]` attribute.
+    pub(crate) wrapper: Option<WrapperInfo>,
 }
 
 #[derive(Serialize)]
@@ -69,7 +114,7 @@ pub(crate) struct ExtractedDocTest {
     doctest_attributes: LangString,
     original_code: String,
     /// `None` if the code syntax is invalid.
-    doctest_code: Option<String>,
+    pub(crate) doctest_code: Option<DocTest>,
     name: String,
 }
 
diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs
index 5e571613d6ff6..3ff6828e52f96 100644
--- a/src/librustdoc/doctest/make.rs
+++ b/src/librustdoc/doctest/make.rs
@@ -196,6 +196,80 @@ pub(crate) struct DocTestBuilder {
     pub(crate) can_be_merged: bool,
 }
 
+/// Contains needed information for doctest to be correctly generated with expected "wrapping".
+pub(crate) struct WrapperInfo {
+    pub(crate) before: String,
+    pub(crate) after: String,
+    pub(crate) returns_result: bool,
+    insert_indent_space: bool,
+}
+
+impl WrapperInfo {
+    fn len(&self) -> usize {
+        self.before.len() + self.after.len()
+    }
+}
+
+/// Contains a doctest information. Can be converted into code with the `to_string()` method.
+pub(crate) enum DocTestWrapResult {
+    Valid {
+        crate_level_code: String,
+        /// This field can be `None` if one of the following conditions is true:
+        ///
+        /// * The doctest's codeblock has the `test_harness` attribute.
+        /// * The doctest has a `main` function.
+        /// * The doctest has the `![no_std]` attribute.
+        wrapper: Option<WrapperInfo>,
+        /// Contains the doctest processed code without the wrappers (which are stored in the
+        /// `wrapper` field).
+        code: String,
+    },
+    /// Contains the original source code.
+    SyntaxError(String),
+}
+
+impl std::string::ToString for DocTestWrapResult {
+    fn to_string(&self) -> String {
+        match self {
+            Self::SyntaxError(s) => s.clone(),
+            Self::Valid { crate_level_code, wrapper, code } => {
+                let mut prog_len = code.len() + crate_level_code.len();
+                if let Some(wrapper) = wrapper {
+                    prog_len += wrapper.len();
+                    if wrapper.insert_indent_space {
+                        prog_len += code.lines().count() * 4;
+                    }
+                }
+                let mut prog = String::with_capacity(prog_len);
+
+                prog.push_str(crate_level_code);
+                if let Some(wrapper) = wrapper {
+                    prog.push_str(&wrapper.before);
+
+                    // add extra 4 spaces for each line to offset the code block
+                    if wrapper.insert_indent_space {
+                        write!(
+                            prog,
+                            "{}",
+                            fmt::from_fn(|f| code
+                                .lines()
+                                .map(|line| fmt::from_fn(move |f| write!(f, "    {line}")))
+                                .joined("\n", f))
+                        )
+                        .unwrap();
+                    } else {
+                        prog.push_str(code);
+                    }
+                    prog.push_str(&wrapper.after);
+                } else {
+                    prog.push_str(code);
+                }
+                prog
+            }
+        }
+    }
+}
+
 impl DocTestBuilder {
     fn invalid(
         global_crate_attrs: Vec<String>,
@@ -228,50 +302,49 @@ impl DocTestBuilder {
         dont_insert_main: bool,
         opts: &GlobalTestOptions,
         crate_name: Option<&str>,
-    ) -> (String, usize) {
+    ) -> (DocTestWrapResult, usize) {
         if self.invalid_ast {
             // If the AST failed to compile, no need to go generate a complete doctest, the error
             // will be better this way.
             debug!("invalid AST:\n{test_code}");
-            return (test_code.to_string(), 0);
+            return (DocTestWrapResult::SyntaxError(test_code.to_string()), 0);
         }
         let mut line_offset = 0;
-        let mut prog = String::new();
-        let everything_else = self.everything_else.trim();
-
+        let mut crate_level_code = String::new();
+        let processed_code = self.everything_else.trim();
         if self.global_crate_attrs.is_empty() {
             // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
             // lints that are commonly triggered in doctests. The crate-level test attributes are
             // commonly used to make tests fail in case they trigger warnings, so having this there in
             // that case may cause some tests to pass when they shouldn't have.
-            prog.push_str("#![allow(unused)]\n");
+            crate_level_code.push_str("#![allow(unused)]\n");
             line_offset += 1;
         }
 
         // Next, any attributes that came from #![doc(test(attr(...)))].
         for attr in &self.global_crate_attrs {
-            prog.push_str(&format!("#![{attr}]\n"));
+            crate_level_code.push_str(&format!("#![{attr}]\n"));
             line_offset += 1;
         }
 
         // Now push any outer attributes from the example, assuming they
         // are intended to be crate attributes.
         if !self.crate_attrs.is_empty() {
-            prog.push_str(&self.crate_attrs);
+            crate_level_code.push_str(&self.crate_attrs);
             if !self.crate_attrs.ends_with('\n') {
-                prog.push('\n');
+                crate_level_code.push('\n');
             }
         }
         if !self.maybe_crate_attrs.is_empty() {
-            prog.push_str(&self.maybe_crate_attrs);
+            crate_level_code.push_str(&self.maybe_crate_attrs);
             if !self.maybe_crate_attrs.ends_with('\n') {
-                prog.push('\n');
+                crate_level_code.push('\n');
             }
         }
         if !self.crates.is_empty() {
-            prog.push_str(&self.crates);
+            crate_level_code.push_str(&self.crates);
             if !self.crates.ends_with('\n') {
-                prog.push('\n');
+                crate_level_code.push('\n');
             }
         }
 
@@ -289,17 +362,20 @@ impl DocTestBuilder {
         {
             // rustdoc implicitly inserts an `extern crate` item for the own crate
             // which may be unused, so we need to allow the lint.
-            prog.push_str("#[allow(unused_extern_crates)]\n");
+            crate_level_code.push_str("#[allow(unused_extern_crates)]\n");
 
-            prog.push_str(&format!("extern crate r#{crate_name};\n"));
+            crate_level_code.push_str(&format!("extern crate r#{crate_name};\n"));
             line_offset += 1;
         }
 
         // FIXME: This code cannot yet handle no_std test cases yet
-        if dont_insert_main || self.has_main_fn || prog.contains("![no_std]") {
-            prog.push_str(everything_else);
+        let wrapper = if dont_insert_main
+            || self.has_main_fn
+            || crate_level_code.contains("![no_std]")
+        {
+            None
         } else {
-            let returns_result = everything_else.ends_with("(())");
+            let returns_result = processed_code.ends_with("(())");
             // Give each doctest main function a unique name.
             // This is for example needed for the tooling around `-C instrument-coverage`.
             let inner_fn_name = if let Some(ref test_id) = self.test_id {
@@ -333,28 +409,22 @@ impl DocTestBuilder {
             // /// ``` <- end of the inner main
             line_offset += 1;
 
-            prog.push_str(&main_pre);
-
-            // add extra 4 spaces for each line to offset the code block
-            if opts.insert_indent_space {
-                write!(
-                    prog,
-                    "{}",
-                    fmt::from_fn(|f| everything_else
-                        .lines()
-                        .map(|line| fmt::from_fn(move |f| write!(f, "    {line}")))
-                        .joined("\n", f))
-                )
-                .unwrap();
-            } else {
-                prog.push_str(everything_else);
-            };
-            prog.push_str(&main_post);
-        }
-
-        debug!("final doctest:\n{prog}");
+            Some(WrapperInfo {
+                before: main_pre,
+                after: main_post,
+                returns_result,
+                insert_indent_space: opts.insert_indent_space,
+            })
+        };
 
-        (prog, line_offset)
+        (
+            DocTestWrapResult::Valid {
+                code: processed_code.to_string(),
+                wrapper,
+                crate_level_code,
+            },
+            line_offset,
+        )
     }
 }
 
diff --git a/src/librustdoc/doctest/tests.rs b/src/librustdoc/doctest/tests.rs
index ce2984ced7904..ccc3e55a33122 100644
--- a/src/librustdoc/doctest/tests.rs
+++ b/src/librustdoc/doctest/tests.rs
@@ -1,6 +1,11 @@
 use std::path::PathBuf;
 
-use super::{BuildDocTestBuilder, GlobalTestOptions};
+use rustc_span::edition::Edition;
+use rustc_span::{DUMMY_SP, FileName};
+
+use super::extracted::ExtractedDocTests;
+use super::{BuildDocTestBuilder, GlobalTestOptions, ScrapedDocTest};
+use crate::html::markdown::LangString;
 
 fn make_test(
     test_code: &str,
@@ -19,9 +24,9 @@ fn make_test(
         builder = builder.test_id(test_id.to_string());
     }
     let doctest = builder.build(None);
-    let (code, line_offset) =
+    let (wrapped, line_offset) =
         doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
-    (code, line_offset)
+    (wrapped.to_string(), line_offset)
 }
 
 /// Default [`GlobalTestOptions`] for these unit tests.
@@ -461,3 +466,51 @@ pub mod outer_module {
     let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
     assert_eq!((output, len), (expected, 2));
 }
+
+fn get_extracted_doctests(code: &str) -> ExtractedDocTests {
+    let opts = default_global_opts("");
+    let mut extractor = ExtractedDocTests::new();
+    extractor.add_test_with_edition(
+        ScrapedDocTest::new(
+            FileName::Custom(String::new()),
+            0,
+            Vec::new(),
+            LangString::default(),
+            code.to_string(),
+            DUMMY_SP,
+            Vec::new(),
+        ),
+        &opts,
+        Edition::Edition2018,
+    );
+    extractor
+}
+
+// Test that `extracted::DocTest::wrapper` is `None` if the doctest has a `main` function.
+#[test]
+fn test_extracted_doctest_wrapper_field() {
+    let extractor = get_extracted_doctests("fn main() {}");
+
+    assert_eq!(extractor.doctests().len(), 1);
+    let doctest_code = extractor.doctests()[0].doctest_code.as_ref().unwrap();
+    assert!(doctest_code.wrapper.is_none());
+}
+
+// Test that `ExtractedDocTest::doctest_code` is `None` if the doctest has syntax error.
+#[test]
+fn test_extracted_doctest_doctest_code_field() {
+    let extractor = get_extracted_doctests("let x +=");
+
+    assert_eq!(extractor.doctests().len(), 1);
+    assert!(extractor.doctests()[0].doctest_code.is_none());
+}
+
+// Test that `extracted::DocTest::wrapper` is `Some` if the doctest needs wrapping.
+#[test]
+fn test_extracted_doctest_wrapper_field_with_info() {
+    let extractor = get_extracted_doctests("let x = 12;");
+
+    assert_eq!(extractor.doctests().len(), 1);
+    let doctest_code = extractor.doctests()[0].doctest_code.as_ref().unwrap();
+    assert!(doctest_code.wrapper.is_some());
+}
diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs
index d3701784f9dff..f626e07b000a6 100644
--- a/src/librustdoc/html/markdown.rs
+++ b/src/librustdoc/html/markdown.rs
@@ -307,7 +307,8 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
                 builder = builder.crate_name(krate);
             }
             let doctest = builder.build(None);
-            let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
+            let (wrapped, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
+            let test = wrapped.to_string();
             let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
 
             let test_escaped = small_url_encode(test);
diff --git a/tests/rustdoc-ui/extract-doctests-result.rs b/tests/rustdoc-ui/extract-doctests-result.rs
new file mode 100644
index 0000000000000..88affb6d33316
--- /dev/null
+++ b/tests/rustdoc-ui/extract-doctests-result.rs
@@ -0,0 +1,11 @@
+// Test to ensure that it generates expected output for `--output-format=doctest` command-line
+// flag.
+
+//@ compile-flags:-Z unstable-options --output-format=doctest
+//@ normalize-stdout: "tests/rustdoc-ui" -> "$$DIR"
+//@ check-pass
+
+//! ```
+//! let x = 12;
+//! Ok(())
+//! ```
diff --git a/tests/rustdoc-ui/extract-doctests-result.stdout b/tests/rustdoc-ui/extract-doctests-result.stdout
new file mode 100644
index 0000000000000..44e6d33c66268
--- /dev/null
+++ b/tests/rustdoc-ui/extract-doctests-result.stdout
@@ -0,0 +1 @@
+{"format_version":2,"doctests":[{"file":"$DIR/extract-doctests-result.rs","line":8,"doctest_attributes":{"original":"","should_panic":false,"no_run":false,"ignore":"None","rust":true,"test_harness":false,"compile_fail":false,"standalone_crate":false,"error_codes":[],"edition":null,"added_css_classes":[],"unknown":[]},"original_code":"let x = 12;\nOk(())","doctest_code":{"crate_level":"#![allow(unused)]\n","code":"let x = 12;\nOk(())","wrapper":{"before":"fn main() { fn _inner() -> core::result::Result<(), impl core::fmt::Debug> {\n","after":"\n} _inner().unwrap() }","returns_result":true}},"name":"$DIR/extract-doctests-result.rs - (line 8)"}]}
\ No newline at end of file
diff --git a/tests/rustdoc-ui/extract-doctests.stdout b/tests/rustdoc-ui/extract-doctests.stdout
index b11531b844ee4..796ecd82f1c93 100644
--- a/tests/rustdoc-ui/extract-doctests.stdout
+++ b/tests/rustdoc-ui/extract-doctests.stdout
@@ -1 +1 @@
-{"format_version":1,"doctests":[{"file":"$DIR/extract-doctests.rs","line":8,"doctest_attributes":{"original":"ignore (checking attributes)","should_panic":false,"no_run":false,"ignore":"All","rust":true,"test_harness":false,"compile_fail":false,"standalone_crate":false,"error_codes":[],"edition":null,"added_css_classes":[],"unknown":[]},"original_code":"let x = 12;\nlet y = 14;","doctest_code":"#![allow(unused)]\nfn main() {\nlet x = 12;\nlet y = 14;\n}","name":"$DIR/extract-doctests.rs - (line 8)"},{"file":"$DIR/extract-doctests.rs","line":13,"doctest_attributes":{"original":"edition2018,compile_fail","should_panic":false,"no_run":true,"ignore":"None","rust":true,"test_harness":false,"compile_fail":true,"standalone_crate":false,"error_codes":[],"edition":"2018","added_css_classes":[],"unknown":[]},"original_code":"let","doctest_code":null,"name":"$DIR/extract-doctests.rs - (line 13)"}]}
\ No newline at end of file
+{"format_version":2,"doctests":[{"file":"$DIR/extract-doctests.rs","line":8,"doctest_attributes":{"original":"ignore (checking attributes)","should_panic":false,"no_run":false,"ignore":"All","rust":true,"test_harness":false,"compile_fail":false,"standalone_crate":false,"error_codes":[],"edition":null,"added_css_classes":[],"unknown":[]},"original_code":"let x = 12;\nlet y = 14;","doctest_code":{"crate_level":"#![allow(unused)]\n","code":"let x = 12;\nlet y = 14;","wrapper":{"before":"fn main() {\n","after":"\n}","returns_result":false}},"name":"$DIR/extract-doctests.rs - (line 8)"},{"file":"$DIR/extract-doctests.rs","line":13,"doctest_attributes":{"original":"edition2018,compile_fail","should_panic":false,"no_run":true,"ignore":"None","rust":true,"test_harness":false,"compile_fail":true,"standalone_crate":false,"error_codes":[],"edition":"2018","added_css_classes":[],"unknown":[]},"original_code":"let","doctest_code":null,"name":"$DIR/extract-doctests.rs - (line 13)"}]}
\ No newline at end of file