about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-05-18 10:53:47 +0000
committerbors <bors@rust-lang.org>2024-05-18 10:53:47 +0000
commitbb97203e37bbdd4588bd684728002077d0073978 (patch)
tree7854c16653291ce69a8cc0af87ed13a9e43b75a2
parent1c90b9fe6eac122b4d3965913b3615f47751a4d3 (diff)
parent7e1dc742d620123f251c696c8aae864a226d1c89 (diff)
downloadrust-bb97203e37bbdd4588bd684728002077d0073978.tar.gz
rust-bb97203e37bbdd4588bd684728002077d0073978.zip
Auto merge of #124611 - Urgau:rustdoc-stdin, r=GuillaumeGomez
Add `-` (stdin) support in rustdoc

This PR adds support for the special `-` input which threats the input as coming from *stdin* instead of being a filepath.

Doing this also makes `rustdoc` consistent with `rustc` and ~~every~~ other tools. Full [motivation](https://github.com/rust-lang/rust/pull/124611#issuecomment-2094234876).

Fixes https://github.com/rust-lang/rust/issues/123671
r? `@fmease`
-rw-r--r--compiler/rustc_session/src/config.rs1
-rw-r--r--src/doc/rustdoc/src/command-line-arguments.md6
-rw-r--r--src/librustdoc/config.rs51
-rw-r--r--src/librustdoc/core.rs4
-rw-r--r--src/librustdoc/doctest.rs4
-rw-r--r--src/librustdoc/lib.rs10
-rw-r--r--src/librustdoc/markdown.rs14
-rw-r--r--tests/run-make/stdin-rustdoc/rmake.rs25
8 files changed, 86 insertions, 29 deletions
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
index f2bdabbf394..7dd9fdf60f9 100644
--- a/compiler/rustc_session/src/config.rs
+++ b/compiler/rustc_session/src/config.rs
@@ -798,6 +798,7 @@ pub enum DumpSolverProofTree {
     Never,
 }
 
+#[derive(Clone)]
 pub enum Input {
     /// Load source code from a file.
     File(PathBuf),
diff --git a/src/doc/rustdoc/src/command-line-arguments.md b/src/doc/rustdoc/src/command-line-arguments.md
index 69fb7e3313f..3e104bdb470 100644
--- a/src/doc/rustdoc/src/command-line-arguments.md
+++ b/src/doc/rustdoc/src/command-line-arguments.md
@@ -417,6 +417,12 @@ When `rustdoc` receives this flag, it will print an extra "Version (version)" in
 the crate root's docs. You can use this flag to differentiate between different versions of your
 library's documentation.
 
+## `-`: load source code from the standard input
+
+If you specify `-` as the INPUT on the command line, then `rustdoc` will read the
+source code from stdin (standard input stream) until the EOF, instead of the file
+system with an otherwise specified path.
+
 ## `@path`: load command-line flags from a path
 
 If you specify `@path` on the command-line, then it will open `path` and read
diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs
index d4468fecba4..012afada1e5 100644
--- a/src/librustdoc/config.rs
+++ b/src/librustdoc/config.rs
@@ -1,6 +1,9 @@
 use std::collections::BTreeMap;
 use std::ffi::OsStr;
 use std::fmt;
+use std::io;
+use std::io::Read;
+use std::path::Path;
 use std::path::PathBuf;
 use std::str::FromStr;
 
@@ -9,14 +12,14 @@ use rustc_session::config::{
     self, parse_crate_types_from_list, parse_externs, parse_target_triple, CrateType,
 };
 use rustc_session::config::{get_cmd_lint_options, nightly_options};
-use rustc_session::config::{
-    CodegenOptions, ErrorOutputType, Externs, JsonUnusedExterns, UnstableOptions,
-};
+use rustc_session::config::{CodegenOptions, ErrorOutputType, Externs, Input};
+use rustc_session::config::{JsonUnusedExterns, UnstableOptions};
 use rustc_session::getopts;
 use rustc_session::lint::Level;
 use rustc_session::search_paths::SearchPath;
 use rustc_session::EarlyDiagCtxt;
 use rustc_span::edition::Edition;
+use rustc_span::FileName;
 use rustc_target::spec::TargetTriple;
 
 use crate::core::new_dcx;
@@ -60,7 +63,7 @@ impl TryFrom<&str> for OutputFormat {
 pub(crate) struct Options {
     // Basic options / Options passed directly to rustc
     /// The crate root or Markdown file to load.
-    pub(crate) input: PathBuf,
+    pub(crate) input: Input,
     /// The name of the crate being documented.
     pub(crate) crate_name: Option<String>,
     /// Whether or not this is a bin crate
@@ -179,7 +182,7 @@ impl fmt::Debug for Options {
         }
 
         f.debug_struct("Options")
-            .field("input", &self.input)
+            .field("input", &self.input.source_name())
             .field("crate_name", &self.crate_name)
             .field("bin_crate", &self.bin_crate)
             .field("proc_macro_crate", &self.proc_macro_crate)
@@ -320,6 +323,23 @@ impl RenderOptions {
     }
 }
 
+/// Create the input (string or file path)
+///
+/// Warning: Return an unrecoverable error in case of error!
+fn make_input(early_dcx: &EarlyDiagCtxt, input: &str) -> Input {
+    if input == "-" {
+        let mut src = String::new();
+        if io::stdin().read_to_string(&mut src).is_err() {
+            // Immediately stop compilation if there was an issue reading
+            // the input (for example if the input stream is not UTF-8).
+            early_dcx.early_fatal("couldn't read from stdin, as it did not contain valid UTF-8");
+        }
+        Input::Str { name: FileName::anon_source_code(&src), input: src }
+    } else {
+        Input::File(PathBuf::from(input))
+    }
+}
+
 impl Options {
     /// Parses the given command-line for options. If an error message or other early-return has
     /// been printed, returns `Err` with the exit code.
@@ -447,15 +467,16 @@ impl Options {
 
         let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
 
-        let input = PathBuf::from(if describe_lints {
+        let input = if describe_lints {
             "" // dummy, this won't be used
-        } else if matches.free.is_empty() {
-            dcx.fatal("missing file operand");
-        } else if matches.free.len() > 1 {
-            dcx.fatal("too many file operands");
         } else {
-            &matches.free[0]
-        });
+            match matches.free.as_slice() {
+                [] => dcx.fatal("missing file operand"),
+                [input] => input,
+                _ => dcx.fatal("too many file operands"),
+            }
+        };
+        let input = make_input(early_dcx, &input);
 
         let externs = parse_externs(early_dcx, matches, &unstable_opts);
         let extern_html_root_urls = match parse_extern_html_roots(matches) {
@@ -792,8 +813,10 @@ impl Options {
     }
 
     /// Returns `true` if the file given as `self.input` is a Markdown file.
-    pub(crate) fn markdown_input(&self) -> bool {
-        self.input.extension().is_some_and(|e| e == "md" || e == "markdown")
+    pub(crate) fn markdown_input(&self) -> Option<&Path> {
+        self.input
+            .opt_path()
+            .filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown"))
     }
 }
 
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 25b78d9598d..feb03b9a823 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -32,7 +32,7 @@ use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions};
 use crate::formats::cache::Cache;
 use crate::passes::{self, Condition::*};
 
-pub(crate) use rustc_session::config::{Input, Options, UnstableOptions};
+pub(crate) use rustc_session::config::{Options, UnstableOptions};
 
 pub(crate) struct DocContext<'tcx> {
     pub(crate) tcx: TyCtxt<'tcx>,
@@ -204,8 +204,6 @@ pub(crate) fn create_config(
     // Add the doc cfg into the doc build.
     cfgs.push("doc".to_string());
 
-    let input = Input::File(input);
-
     // By default, rustdoc ignores all lints.
     // Specifically unblock lints relevant to documentation or the lint machinery itself.
     let mut lints_to_show = vec![
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs
index 0ad4c9c2346..a4a960219bd 100644
--- a/src/librustdoc/doctest.rs
+++ b/src/librustdoc/doctest.rs
@@ -93,8 +93,6 @@ pub(crate) fn run(
     dcx: &rustc_errors::DiagCtxt,
     options: RustdocOptions,
 ) -> Result<(), ErrorGuaranteed> {
-    let input = config::Input::File(options.input.clone());
-
     let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
 
     // See core::create_config for what's going on here.
@@ -140,7 +138,7 @@ pub(crate) fn run(
         opts: sessopts,
         crate_cfg: cfgs,
         crate_check_cfg: options.check_cfgs.clone(),
-        input,
+        input: options.input.clone(),
         output_file: None,
         output_dir: None,
         file_loader: None,
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index f2a7518b4ce..0650afb90c7 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -730,10 +730,10 @@ fn main_args(
         core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts);
 
     match (options.should_test, options.markdown_input()) {
-        (true, true) => return wrap_return(&diag, markdown::test(options)),
-        (true, false) => return doctest::run(&diag, options),
-        (false, true) => {
-            let input = options.input.clone();
+        (true, Some(_)) => return wrap_return(&diag, markdown::test(options)),
+        (true, None) => return doctest::run(&diag, options),
+        (false, Some(input)) => {
+            let input = input.to_owned();
             let edition = options.edition;
             let config = core::create_config(options, &render_options, using_internal_features);
 
@@ -747,7 +747,7 @@ fn main_args(
                 }),
             );
         }
-        (false, false) => {}
+        (false, None) => {}
     }
 
     // need to move these items separately because we lose them by the time the closure is called,
diff --git a/src/librustdoc/markdown.rs b/src/librustdoc/markdown.rs
index dcd2cf02a30..7289ed56dc7 100644
--- a/src/librustdoc/markdown.rs
+++ b/src/librustdoc/markdown.rs
@@ -144,8 +144,14 @@ pub(crate) fn render<P: AsRef<Path>>(
 
 /// Runs any tests/code examples in the markdown file `input`.
 pub(crate) fn test(options: Options) -> Result<(), String> {
-    let input_str = read_to_string(&options.input)
-        .map_err(|err| format!("{input}: {err}", input = options.input.display()))?;
+    use rustc_session::config::Input;
+    let input_str = match &options.input {
+        Input::File(path) => {
+            read_to_string(&path).map_err(|err| format!("{}: {err}", path.display()))?
+        }
+        Input::Str { name: _, input } => input.clone(),
+    };
+
     let mut opts = GlobalTestOptions::default();
     opts.no_crate_inject = true;
 
@@ -155,12 +161,12 @@ pub(crate) fn test(options: Options) -> Result<(), String> {
     generate_args_file(&file_path, &options)?;
 
     let mut collector = Collector::new(
-        options.input.display().to_string(),
+        options.input.filestem().to_string(),
         options.clone(),
         true,
         opts,
         None,
-        Some(options.input),
+        options.input.opt_path().map(ToOwned::to_owned),
         options.enable_per_target_ignores,
         file_path,
     );
diff --git a/tests/run-make/stdin-rustdoc/rmake.rs b/tests/run-make/stdin-rustdoc/rmake.rs
new file mode 100644
index 00000000000..584a610ed63
--- /dev/null
+++ b/tests/run-make/stdin-rustdoc/rmake.rs
@@ -0,0 +1,25 @@
+//! This test checks rustdoc `-` (stdin) handling
+
+use run_make_support::{rustdoc, tmp_dir};
+
+static INPUT: &str = r#"
+//! ```
+//! dbg!(());
+//! ```
+pub struct F;
+"#;
+
+fn main() {
+    let tmp_dir = tmp_dir();
+    let out_dir = tmp_dir.join("doc");
+
+    // rustdoc -
+    rustdoc().arg("-").out_dir(&out_dir).stdin(INPUT).run();
+    assert!(out_dir.join("rust_out/struct.F.html").try_exists().unwrap());
+
+    // rustdoc --test -
+    rustdoc().arg("--test").arg("-").stdin(INPUT).run();
+
+    // rustdoc file.rs -
+    rustdoc().arg("file.rs").arg("-").run_fail();
+}