|  | use std::collections::BTreeMap; | 
|  | use std::ffi::OsStr; | 
|  | use std::io::Read; | 
|  | use std::path::{Path, PathBuf}; | 
|  | use std::str::FromStr; | 
|  | use std::{fmt, io}; | 
|  |  | 
|  | use rustc_data_structures::fx::FxIndexMap; | 
|  | use rustc_errors::DiagCtxtHandle; | 
|  | use rustc_session::config::{ | 
|  | self, CodegenOptions, CrateType, ErrorOutputType, Externs, Input, JsonUnusedExterns, | 
|  | OptionsTargetModifiers, Sysroot, UnstableOptions, get_cmd_lint_options, nightly_options, | 
|  | parse_crate_types_from_list, parse_externs, parse_target_triple, | 
|  | }; | 
|  | use rustc_session::lint::Level; | 
|  | use rustc_session::search_paths::SearchPath; | 
|  | use rustc_session::{EarlyDiagCtxt, getopts}; | 
|  | use rustc_span::FileName; | 
|  | use rustc_span::edition::Edition; | 
|  | use rustc_target::spec::TargetTuple; | 
|  |  | 
|  | use crate::core::new_dcx; | 
|  | use crate::externalfiles::ExternalHtml; | 
|  | use crate::html::markdown::IdMap; | 
|  | use crate::html::render::StylePath; | 
|  | use crate::html::static_files; | 
|  | use crate::passes::{self, Condition}; | 
|  | use crate::scrape_examples::{AllCallLocations, ScrapeExamplesOptions}; | 
|  | use crate::{html, opts, theme}; | 
|  |  | 
|  | #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] | 
|  | pub(crate) enum OutputFormat { | 
|  | Json, | 
|  | #[default] | 
|  | Html, | 
|  | Doctest, | 
|  | } | 
|  |  | 
|  | impl OutputFormat { | 
|  | pub(crate) fn is_json(&self) -> bool { | 
|  | matches!(self, OutputFormat::Json) | 
|  | } | 
|  | } | 
|  |  | 
|  | impl TryFrom<&str> for OutputFormat { | 
|  | type Error = String; | 
|  |  | 
|  | fn try_from(value: &str) -> Result<Self, Self::Error> { | 
|  | match value { | 
|  | "json" => Ok(OutputFormat::Json), | 
|  | "html" => Ok(OutputFormat::Html), | 
|  | "doctest" => Ok(OutputFormat::Doctest), | 
|  | _ => Err(format!("unknown output format `{value}`")), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Either an input crate, markdown file, or nothing (--merge=finalize). | 
|  | pub(crate) enum InputMode { | 
|  | /// The `--merge=finalize` step does not need an input crate to rustdoc. | 
|  | NoInputMergeFinalize, | 
|  | /// A crate or markdown file. | 
|  | HasFile(Input), | 
|  | } | 
|  |  | 
|  | /// Configuration options for rustdoc. | 
|  | #[derive(Clone)] | 
|  | pub(crate) struct Options { | 
|  | // Basic options / Options passed directly to rustc | 
|  | /// The name of the crate being documented. | 
|  | pub(crate) crate_name: Option<String>, | 
|  | /// Whether or not this is a bin crate | 
|  | pub(crate) bin_crate: bool, | 
|  | /// Whether or not this is a proc-macro crate | 
|  | pub(crate) proc_macro_crate: bool, | 
|  | /// How to format errors and warnings. | 
|  | pub(crate) error_format: ErrorOutputType, | 
|  | /// Width of output buffer to truncate errors appropriately. | 
|  | pub(crate) diagnostic_width: Option<usize>, | 
|  | /// Library search paths to hand to the compiler. | 
|  | pub(crate) libs: Vec<SearchPath>, | 
|  | /// Library search paths strings to hand to the compiler. | 
|  | pub(crate) lib_strs: Vec<String>, | 
|  | /// The list of external crates to link against. | 
|  | pub(crate) externs: Externs, | 
|  | /// The list of external crates strings to link against. | 
|  | pub(crate) extern_strs: Vec<String>, | 
|  | /// List of `cfg` flags to hand to the compiler. Always includes `rustdoc`. | 
|  | pub(crate) cfgs: Vec<String>, | 
|  | /// List of check cfg flags to hand to the compiler. | 
|  | pub(crate) check_cfgs: Vec<String>, | 
|  | /// Codegen options to hand to the compiler. | 
|  | pub(crate) codegen_options: CodegenOptions, | 
|  | /// Codegen options strings to hand to the compiler. | 
|  | pub(crate) codegen_options_strs: Vec<String>, | 
|  | /// Unstable (`-Z`) options to pass to the compiler. | 
|  | pub(crate) unstable_opts: UnstableOptions, | 
|  | /// Unstable (`-Z`) options strings to pass to the compiler. | 
|  | pub(crate) unstable_opts_strs: Vec<String>, | 
|  | /// The target used to compile the crate against. | 
|  | pub(crate) target: TargetTuple, | 
|  | /// Edition used when reading the crate. Defaults to "2015". Also used by default when | 
|  | /// compiling doctests from the crate. | 
|  | pub(crate) edition: Edition, | 
|  | /// The path to the sysroot. Used during the compilation process. | 
|  | pub(crate) sysroot: Sysroot, | 
|  | /// Lint information passed over the command-line. | 
|  | pub(crate) lint_opts: Vec<(String, Level)>, | 
|  | /// Whether to ask rustc to describe the lints it knows. | 
|  | pub(crate) describe_lints: bool, | 
|  | /// What level to cap lints at. | 
|  | pub(crate) lint_cap: Option<Level>, | 
|  |  | 
|  | // Options specific to running doctests | 
|  | /// Whether we should run doctests instead of generating docs. | 
|  | pub(crate) should_test: bool, | 
|  | /// List of arguments to pass to the test harness, if running tests. | 
|  | pub(crate) test_args: Vec<String>, | 
|  | /// The working directory in which to run tests. | 
|  | pub(crate) test_run_directory: Option<PathBuf>, | 
|  | /// Optional path to persist the doctest executables to, defaults to a | 
|  | /// temporary directory if not set. | 
|  | pub(crate) persist_doctests: Option<PathBuf>, | 
|  | /// Runtool to run doctests with | 
|  | pub(crate) test_runtool: Option<String>, | 
|  | /// Arguments to pass to the runtool | 
|  | pub(crate) test_runtool_args: Vec<String>, | 
|  | /// Do not run doctests, compile them if should_test is active. | 
|  | pub(crate) no_run: bool, | 
|  | /// What sources are being mapped. | 
|  | pub(crate) remap_path_prefix: Vec<(PathBuf, PathBuf)>, | 
|  |  | 
|  | /// The path to a rustc-like binary to build tests with. If not set, we | 
|  | /// default to loading from `$sysroot/bin/rustc`. | 
|  | pub(crate) test_builder: Option<PathBuf>, | 
|  |  | 
|  | /// Run these wrapper instead of rustc directly | 
|  | pub(crate) test_builder_wrappers: Vec<PathBuf>, | 
|  |  | 
|  | // Options that affect the documentation process | 
|  | /// Whether to run the `calculate-doc-coverage` pass, which counts the number of public items | 
|  | /// with and without documentation. | 
|  | pub(crate) show_coverage: bool, | 
|  |  | 
|  | // Options that alter generated documentation pages | 
|  | /// Crate version to note on the sidebar of generated docs. | 
|  | pub(crate) crate_version: Option<String>, | 
|  | /// The format that we output when rendering. | 
|  | /// | 
|  | /// Currently used only for the `--show-coverage` option. | 
|  | pub(crate) output_format: OutputFormat, | 
|  | /// If this option is set to `true`, rustdoc will only run checks and not generate | 
|  | /// documentation. | 
|  | pub(crate) run_check: bool, | 
|  | /// Whether doctests should emit unused externs | 
|  | pub(crate) json_unused_externs: JsonUnusedExterns, | 
|  | /// Whether to skip capturing stdout and stderr of tests. | 
|  | pub(crate) nocapture: bool, | 
|  |  | 
|  | /// Configuration for scraping examples from the current crate. If this option is Some(..) then | 
|  | /// the compiler will scrape examples and not generate documentation. | 
|  | pub(crate) scrape_examples_options: Option<ScrapeExamplesOptions>, | 
|  |  | 
|  | /// Note: this field is duplicated in `RenderOptions` because it's useful | 
|  | /// to have it in both places. | 
|  | pub(crate) unstable_features: rustc_feature::UnstableFeatures, | 
|  |  | 
|  | /// All commandline args used to invoke the compiler, with @file args fully expanded. | 
|  | /// This will only be used within debug info, e.g. in the pdb file on windows | 
|  | /// This is mainly useful for other tools that reads that debuginfo to figure out | 
|  | /// how to call the compiler with the same arguments. | 
|  | pub(crate) expanded_args: Vec<String>, | 
|  |  | 
|  | /// Arguments to be used when compiling doctests. | 
|  | pub(crate) doctest_build_args: Vec<String>, | 
|  | } | 
|  |  | 
|  | impl fmt::Debug for Options { | 
|  | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
|  | struct FmtExterns<'a>(&'a Externs); | 
|  |  | 
|  | impl fmt::Debug for FmtExterns<'_> { | 
|  | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
|  | f.debug_map().entries(self.0.iter()).finish() | 
|  | } | 
|  | } | 
|  |  | 
|  | f.debug_struct("Options") | 
|  | .field("crate_name", &self.crate_name) | 
|  | .field("bin_crate", &self.bin_crate) | 
|  | .field("proc_macro_crate", &self.proc_macro_crate) | 
|  | .field("error_format", &self.error_format) | 
|  | .field("libs", &self.libs) | 
|  | .field("externs", &FmtExterns(&self.externs)) | 
|  | .field("cfgs", &self.cfgs) | 
|  | .field("check-cfgs", &self.check_cfgs) | 
|  | .field("codegen_options", &"...") | 
|  | .field("unstable_options", &"...") | 
|  | .field("target", &self.target) | 
|  | .field("edition", &self.edition) | 
|  | .field("sysroot", &self.sysroot) | 
|  | .field("lint_opts", &self.lint_opts) | 
|  | .field("describe_lints", &self.describe_lints) | 
|  | .field("lint_cap", &self.lint_cap) | 
|  | .field("should_test", &self.should_test) | 
|  | .field("test_args", &self.test_args) | 
|  | .field("test_run_directory", &self.test_run_directory) | 
|  | .field("persist_doctests", &self.persist_doctests) | 
|  | .field("show_coverage", &self.show_coverage) | 
|  | .field("crate_version", &self.crate_version) | 
|  | .field("test_runtool", &self.test_runtool) | 
|  | .field("test_runtool_args", &self.test_runtool_args) | 
|  | .field("run_check", &self.run_check) | 
|  | .field("no_run", &self.no_run) | 
|  | .field("test_builder_wrappers", &self.test_builder_wrappers) | 
|  | .field("remap-file-prefix", &self.remap_path_prefix) | 
|  | .field("nocapture", &self.nocapture) | 
|  | .field("scrape_examples_options", &self.scrape_examples_options) | 
|  | .field("unstable_features", &self.unstable_features) | 
|  | .finish() | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Configuration options for the HTML page-creation process. | 
|  | #[derive(Clone, Debug)] | 
|  | pub(crate) struct RenderOptions { | 
|  | /// Output directory to generate docs into. Defaults to `doc`. | 
|  | pub(crate) output: PathBuf, | 
|  | /// External files to insert into generated pages. | 
|  | pub(crate) external_html: ExternalHtml, | 
|  | /// A pre-populated `IdMap` with the default headings and any headings added by Markdown files | 
|  | /// processed by `external_html`. | 
|  | pub(crate) id_map: IdMap, | 
|  | /// If present, playground URL to use in the "Run" button added to code samples. | 
|  | /// | 
|  | /// Be aware: This option can come both from the CLI and from crate attributes! | 
|  | pub(crate) playground_url: Option<String>, | 
|  | /// What sorting mode to use for module pages. | 
|  | /// `ModuleSorting::Alphabetical` by default. | 
|  | pub(crate) module_sorting: ModuleSorting, | 
|  | /// List of themes to extend the docs with. Original argument name is included to assist in | 
|  | /// displaying errors if it fails a theme check. | 
|  | pub(crate) themes: Vec<StylePath>, | 
|  | /// If present, CSS file that contains rules to add to the default CSS. | 
|  | pub(crate) extension_css: Option<PathBuf>, | 
|  | /// A map of crate names to the URL to use instead of querying the crate's `html_root_url`. | 
|  | pub(crate) extern_html_root_urls: BTreeMap<String, String>, | 
|  | /// Whether to give precedence to `html_root_url` or `--extern-html-root-url`. | 
|  | pub(crate) extern_html_root_takes_precedence: bool, | 
|  | /// A map of the default settings (values are as for DOM storage API). Keys should lack the | 
|  | /// `rustdoc-` prefix. | 
|  | pub(crate) default_settings: FxIndexMap<String, String>, | 
|  | /// If present, suffix added to CSS/JavaScript files when referencing them in generated pages. | 
|  | pub(crate) resource_suffix: String, | 
|  | /// Whether to create an index page in the root of the output directory. If this is true but | 
|  | /// `enable_index_page` is None, generate a static listing of crates instead. | 
|  | pub(crate) enable_index_page: bool, | 
|  | /// A file to use as the index page at the root of the output directory. Overrides | 
|  | /// `enable_index_page` to be true if set. | 
|  | pub(crate) index_page: Option<PathBuf>, | 
|  | /// An optional path to use as the location of static files. If not set, uses combinations of | 
|  | /// `../` to reach the documentation root. | 
|  | pub(crate) static_root_path: Option<String>, | 
|  |  | 
|  | // Options specific to reading standalone Markdown files | 
|  | /// Whether to generate a table of contents on the output file when reading a standalone | 
|  | /// Markdown file. | 
|  | pub(crate) markdown_no_toc: bool, | 
|  | /// Additional CSS files to link in pages generated from standalone Markdown files. | 
|  | pub(crate) markdown_css: Vec<String>, | 
|  | /// If present, playground URL to use in the "Run" button added to code samples generated from | 
|  | /// standalone Markdown files. If not present, `playground_url` is used. | 
|  | pub(crate) markdown_playground_url: Option<String>, | 
|  | /// Document items that have lower than `pub` visibility. | 
|  | pub(crate) document_private: bool, | 
|  | /// Document items that have `doc(hidden)`. | 
|  | pub(crate) document_hidden: bool, | 
|  | /// If `true`, generate a JSON file in the crate folder instead of HTML redirection files. | 
|  | pub(crate) generate_redirect_map: bool, | 
|  | /// Show the memory layout of types in the docs. | 
|  | pub(crate) show_type_layout: bool, | 
|  | /// Note: this field is duplicated in `Options` because it's useful to have | 
|  | /// it in both places. | 
|  | pub(crate) unstable_features: rustc_feature::UnstableFeatures, | 
|  | pub(crate) emit: Vec<EmitType>, | 
|  | /// If `true`, HTML source pages will generate links for items to their definition. | 
|  | pub(crate) generate_link_to_definition: bool, | 
|  | /// Set of function-call locations to include as examples | 
|  | pub(crate) call_locations: AllCallLocations, | 
|  | /// If `true`, Context::init will not emit shared files. | 
|  | pub(crate) no_emit_shared: bool, | 
|  | /// If `true`, HTML source code pages won't be generated. | 
|  | pub(crate) html_no_source: bool, | 
|  | /// This field is only used for the JSON output. If it's set to true, no file will be created | 
|  | /// and content will be displayed in stdout directly. | 
|  | pub(crate) output_to_stdout: bool, | 
|  | /// Whether we should read or write rendered cross-crate info in the doc root. | 
|  | pub(crate) should_merge: ShouldMerge, | 
|  | /// Path to crate-info for external crates. | 
|  | pub(crate) include_parts_dir: Vec<PathToParts>, | 
|  | /// Where to write crate-info | 
|  | pub(crate) parts_out_dir: Option<PathToParts>, | 
|  | /// disable minification of CSS/JS | 
|  | pub(crate) disable_minification: bool, | 
|  | } | 
|  |  | 
|  | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | 
|  | pub(crate) enum ModuleSorting { | 
|  | DeclarationOrder, | 
|  | Alphabetical, | 
|  | } | 
|  |  | 
|  | #[derive(Clone, Debug, PartialEq, Eq)] | 
|  | pub(crate) enum EmitType { | 
|  | Unversioned, | 
|  | Toolchain, | 
|  | InvocationSpecific, | 
|  | DepInfo(Option<PathBuf>), | 
|  | } | 
|  |  | 
|  | impl FromStr for EmitType { | 
|  | type Err = (); | 
|  |  | 
|  | fn from_str(s: &str) -> Result<Self, Self::Err> { | 
|  | match s { | 
|  | "unversioned-shared-resources" => Ok(Self::Unversioned), | 
|  | "toolchain-shared-resources" => Ok(Self::Toolchain), | 
|  | "invocation-specific" => Ok(Self::InvocationSpecific), | 
|  | "dep-info" => Ok(Self::DepInfo(None)), | 
|  | option => { | 
|  | if let Some(file) = option.strip_prefix("dep-info=") { | 
|  | Ok(Self::DepInfo(Some(Path::new(file).into()))) | 
|  | } else { | 
|  | Err(()) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | impl RenderOptions { | 
|  | pub(crate) fn should_emit_crate(&self) -> bool { | 
|  | self.emit.is_empty() || self.emit.contains(&EmitType::InvocationSpecific) | 
|  | } | 
|  |  | 
|  | pub(crate) fn dep_info(&self) -> Option<Option<&Path>> { | 
|  | for emit in &self.emit { | 
|  | if let EmitType::DepInfo(file) = emit { | 
|  | return Some(file.as_deref()); | 
|  | } | 
|  | } | 
|  | None | 
|  | } | 
|  | } | 
|  |  | 
|  | /// 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. | 
|  | pub(crate) fn from_matches( | 
|  | early_dcx: &mut EarlyDiagCtxt, | 
|  | matches: &getopts::Matches, | 
|  | args: Vec<String>, | 
|  | ) -> Option<(InputMode, Options, RenderOptions)> { | 
|  | // Check for unstable options. | 
|  | nightly_options::check_nightly_options(early_dcx, matches, &opts()); | 
|  |  | 
|  | if args.is_empty() || matches.opt_present("h") || matches.opt_present("help") { | 
|  | crate::usage("rustdoc"); | 
|  | return None; | 
|  | } else if matches.opt_present("version") { | 
|  | rustc_driver::version!(&early_dcx, "rustdoc", matches); | 
|  | return None; | 
|  | } | 
|  |  | 
|  | if rustc_driver::describe_flag_categories(early_dcx, matches) { | 
|  | return None; | 
|  | } | 
|  |  | 
|  | let color = config::parse_color(early_dcx, matches); | 
|  | let config::JsonConfig { json_rendered, json_unused_externs, json_color, .. } = | 
|  | config::parse_json(early_dcx, matches); | 
|  | let error_format = | 
|  | config::parse_error_format(early_dcx, matches, color, json_color, json_rendered); | 
|  | let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_default(); | 
|  |  | 
|  | let mut target_modifiers = BTreeMap::<OptionsTargetModifiers, String>::new(); | 
|  | let codegen_options = CodegenOptions::build(early_dcx, matches, &mut target_modifiers); | 
|  | let unstable_opts = UnstableOptions::build(early_dcx, matches, &mut target_modifiers); | 
|  |  | 
|  | let remap_path_prefix = match parse_remap_path_prefix(matches) { | 
|  | Ok(prefix_mappings) => prefix_mappings, | 
|  | Err(err) => { | 
|  | early_dcx.early_fatal(err); | 
|  | } | 
|  | }; | 
|  |  | 
|  | let dcx = new_dcx(error_format, None, diagnostic_width, &unstable_opts); | 
|  | let dcx = dcx.handle(); | 
|  |  | 
|  | // check for deprecated options | 
|  | check_deprecated_options(matches, dcx); | 
|  |  | 
|  | if matches.opt_strs("passes") == ["list"] { | 
|  | println!("Available passes for running rustdoc:"); | 
|  | for pass in passes::PASSES { | 
|  | println!("{:>20} - {}", pass.name, pass.description); | 
|  | } | 
|  | println!("\nDefault passes for rustdoc:"); | 
|  | for p in passes::DEFAULT_PASSES { | 
|  | print!("{:>20}", p.pass.name); | 
|  | println_condition(p.condition); | 
|  | } | 
|  |  | 
|  | if nightly_options::match_is_nightly_build(matches) { | 
|  | println!("\nPasses run with `--show-coverage`:"); | 
|  | for p in passes::COVERAGE_PASSES { | 
|  | print!("{:>20}", p.pass.name); | 
|  | println_condition(p.condition); | 
|  | } | 
|  | } | 
|  |  | 
|  | fn println_condition(condition: Condition) { | 
|  | use Condition::*; | 
|  | match condition { | 
|  | Always => println!(), | 
|  | WhenDocumentPrivate => println!("  (when --document-private-items)"), | 
|  | WhenNotDocumentPrivate => println!("  (when not --document-private-items)"), | 
|  | WhenNotDocumentHidden => println!("  (when not --document-hidden-items)"), | 
|  | } | 
|  | } | 
|  |  | 
|  | return None; | 
|  | } | 
|  |  | 
|  | let mut emit = Vec::new(); | 
|  | for list in matches.opt_strs("emit") { | 
|  | for kind in list.split(',') { | 
|  | match kind.parse() { | 
|  | Ok(kind) => emit.push(kind), | 
|  | Err(()) => dcx.fatal(format!("unrecognized emission type: {kind}")), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | let show_coverage = matches.opt_present("show-coverage"); | 
|  | let output_format_s = matches.opt_str("output-format"); | 
|  | let output_format = match output_format_s { | 
|  | Some(ref s) => match OutputFormat::try_from(s.as_str()) { | 
|  | Ok(out_fmt) => out_fmt, | 
|  | Err(e) => dcx.fatal(e), | 
|  | }, | 
|  | None => OutputFormat::default(), | 
|  | }; | 
|  |  | 
|  | // check for `--output-format=json` | 
|  | match ( | 
|  | output_format_s.as_ref().map(|_| output_format), | 
|  | show_coverage, | 
|  | nightly_options::is_unstable_enabled(matches), | 
|  | ) { | 
|  | (None | Some(OutputFormat::Json), true, _) => {} | 
|  | (_, true, _) => { | 
|  | dcx.fatal(format!( | 
|  | "`--output-format={}` is not supported for the `--show-coverage` option", | 
|  | output_format_s.unwrap_or_default(), | 
|  | )); | 
|  | } | 
|  | // If `-Zunstable-options` is used, nothing to check after this point. | 
|  | (_, false, true) => {} | 
|  | (None | Some(OutputFormat::Html), false, _) => {} | 
|  | (Some(OutputFormat::Json), false, false) => { | 
|  | dcx.fatal( | 
|  | "the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578)", | 
|  | ); | 
|  | } | 
|  | (Some(OutputFormat::Doctest), false, false) => { | 
|  | dcx.fatal( | 
|  | "the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/134529)", | 
|  | ); | 
|  | } | 
|  | } | 
|  |  | 
|  | let to_check = matches.opt_strs("check-theme"); | 
|  | if !to_check.is_empty() { | 
|  | let mut content = | 
|  | std::str::from_utf8(static_files::STATIC_FILES.rustdoc_css.src_bytes).unwrap(); | 
|  | if let Some((_, inside)) = content.split_once("/* Begin theme: light */") { | 
|  | content = inside; | 
|  | } | 
|  | if let Some((inside, _)) = content.split_once("/* End theme: light */") { | 
|  | content = inside; | 
|  | } | 
|  | let paths = match theme::load_css_paths(content) { | 
|  | Ok(p) => p, | 
|  | Err(e) => dcx.fatal(e), | 
|  | }; | 
|  | let mut errors = 0; | 
|  |  | 
|  | println!("rustdoc: [check-theme] Starting tests! (Ignoring all other arguments)"); | 
|  | for theme_file in to_check.iter() { | 
|  | print!(" - Checking \"{theme_file}\"..."); | 
|  | let (success, differences) = theme::test_theme_against(theme_file, &paths, dcx); | 
|  | if !differences.is_empty() || !success { | 
|  | println!(" FAILED"); | 
|  | errors += 1; | 
|  | if !differences.is_empty() { | 
|  | println!("{}", differences.join("\n")); | 
|  | } | 
|  | } else { | 
|  | println!(" OK"); | 
|  | } | 
|  | } | 
|  | if errors != 0 { | 
|  | dcx.fatal("[check-theme] one or more tests failed"); | 
|  | } | 
|  | return None; | 
|  | } | 
|  |  | 
|  | let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); | 
|  |  | 
|  | let input = if describe_lints { | 
|  | InputMode::HasFile(make_input(early_dcx, "")) | 
|  | } else { | 
|  | match matches.free.as_slice() { | 
|  | [] if matches.opt_str("merge").as_deref() == Some("finalize") => { | 
|  | InputMode::NoInputMergeFinalize | 
|  | } | 
|  | [] => dcx.fatal("missing file operand"), | 
|  | [input] => InputMode::HasFile(make_input(early_dcx, input)), | 
|  | _ => dcx.fatal("too many file operands"), | 
|  | } | 
|  | }; | 
|  |  | 
|  | let externs = parse_externs(early_dcx, matches, &unstable_opts); | 
|  | let extern_html_root_urls = match parse_extern_html_roots(matches) { | 
|  | Ok(ex) => ex, | 
|  | Err(err) => dcx.fatal(err), | 
|  | }; | 
|  |  | 
|  | let parts_out_dir = | 
|  | match matches.opt_str("parts-out-dir").map(PathToParts::from_flag).transpose() { | 
|  | Ok(parts_out_dir) => parts_out_dir, | 
|  | Err(e) => dcx.fatal(e), | 
|  | }; | 
|  | let include_parts_dir = match parse_include_parts_dir(matches) { | 
|  | Ok(include_parts_dir) => include_parts_dir, | 
|  | Err(e) => dcx.fatal(e), | 
|  | }; | 
|  |  | 
|  | let default_settings: Vec<Vec<(String, String)>> = vec![ | 
|  | matches | 
|  | .opt_str("default-theme") | 
|  | .iter() | 
|  | .flat_map(|theme| { | 
|  | vec![ | 
|  | ("use-system-theme".to_string(), "false".to_string()), | 
|  | ("theme".to_string(), theme.to_string()), | 
|  | ] | 
|  | }) | 
|  | .collect(), | 
|  | matches | 
|  | .opt_strs("default-setting") | 
|  | .iter() | 
|  | .map(|s| match s.split_once('=') { | 
|  | None => (s.clone(), "true".to_string()), | 
|  | Some((k, v)) => (k.to_string(), v.to_string()), | 
|  | }) | 
|  | .collect(), | 
|  | ]; | 
|  | let default_settings = default_settings | 
|  | .into_iter() | 
|  | .flatten() | 
|  | .map( | 
|  | // The keys here become part of `data-` attribute names in the generated HTML.  The | 
|  | // browser does a strange mapping when converting them into attributes on the | 
|  | // `dataset` property on the DOM HTML Node: | 
|  | //   https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset | 
|  | // | 
|  | // The original key values we have are the same as the DOM storage API keys and the | 
|  | // command line options, so contain `-`.  Our JavaScript needs to be able to look | 
|  | // these values up both in `dataset` and in the storage API, so it needs to be able | 
|  | // to convert the names back and forth.  Despite doing this kebab-case to | 
|  | // StudlyCaps transformation automatically, the JS DOM API does not provide a | 
|  | // mechanism for doing just the transformation on a string.  So we want to avoid | 
|  | // the StudlyCaps representation in the `dataset` property. | 
|  | // | 
|  | // We solve this by replacing all the `-`s with `_`s.  We do that here, when we | 
|  | // generate the `data-` attributes, and in the JS, when we look them up.  (See | 
|  | // `getSettingValue` in `storage.js.`) Converting `-` to `_` is simple in JS. | 
|  | // | 
|  | // The values will be HTML-escaped by the default Tera escaping. | 
|  | |(k, v)| (k.replace('-', "_"), v), | 
|  | ) | 
|  | .collect(); | 
|  |  | 
|  | let test_args = matches.opt_strs("test-args"); | 
|  | let test_args: Vec<String> = | 
|  | test_args.iter().flat_map(|s| s.split_whitespace()).map(|s| s.to_string()).collect(); | 
|  |  | 
|  | let should_test = matches.opt_present("test"); | 
|  | let no_run = matches.opt_present("no-run"); | 
|  |  | 
|  | if !should_test && no_run { | 
|  | dcx.fatal("the `--test` flag must be passed to enable `--no-run`"); | 
|  | } | 
|  |  | 
|  | let mut output_to_stdout = false; | 
|  | let test_builder_wrappers = | 
|  | matches.opt_strs("test-builder-wrapper").iter().map(PathBuf::from).collect(); | 
|  | let output = match (matches.opt_str("out-dir"), matches.opt_str("output")) { | 
|  | (Some(_), Some(_)) => { | 
|  | dcx.fatal("cannot use both 'out-dir' and 'output' at once"); | 
|  | } | 
|  | (Some(out_dir), None) | (None, Some(out_dir)) => { | 
|  | output_to_stdout = out_dir == "-"; | 
|  | PathBuf::from(out_dir) | 
|  | } | 
|  | (None, None) => PathBuf::from("doc"), | 
|  | }; | 
|  |  | 
|  | let cfgs = matches.opt_strs("cfg"); | 
|  | let check_cfgs = matches.opt_strs("check-cfg"); | 
|  |  | 
|  | let extension_css = matches.opt_str("e").map(|s| PathBuf::from(&s)); | 
|  |  | 
|  | if let Some(ref p) = extension_css | 
|  | && !p.is_file() | 
|  | { | 
|  | dcx.fatal("option --extend-css argument must be a file"); | 
|  | } | 
|  |  | 
|  | let mut themes = Vec::new(); | 
|  | if matches.opt_present("theme") { | 
|  | let mut content = | 
|  | std::str::from_utf8(static_files::STATIC_FILES.rustdoc_css.src_bytes).unwrap(); | 
|  | if let Some((_, inside)) = content.split_once("/* Begin theme: light */") { | 
|  | content = inside; | 
|  | } | 
|  | if let Some((inside, _)) = content.split_once("/* End theme: light */") { | 
|  | content = inside; | 
|  | } | 
|  | let paths = match theme::load_css_paths(content) { | 
|  | Ok(p) => p, | 
|  | Err(e) => dcx.fatal(e), | 
|  | }; | 
|  |  | 
|  | for (theme_file, theme_s) in | 
|  | matches.opt_strs("theme").iter().map(|s| (PathBuf::from(&s), s.to_owned())) | 
|  | { | 
|  | if !theme_file.is_file() { | 
|  | dcx.struct_fatal(format!("invalid argument: \"{theme_s}\"")) | 
|  | .with_help("arguments to --theme must be files") | 
|  | .emit(); | 
|  | } | 
|  | if theme_file.extension() != Some(OsStr::new("css")) { | 
|  | dcx.struct_fatal(format!("invalid argument: \"{theme_s}\"")) | 
|  | .with_help("arguments to --theme must have a .css extension") | 
|  | .emit(); | 
|  | } | 
|  | let (success, ret) = theme::test_theme_against(&theme_file, &paths, dcx); | 
|  | if !success { | 
|  | dcx.fatal(format!("error loading theme file: \"{theme_s}\"")); | 
|  | } else if !ret.is_empty() { | 
|  | dcx.struct_warn(format!( | 
|  | "theme file \"{theme_s}\" is missing CSS rules from the default theme", | 
|  | )) | 
|  | .with_warn("the theme may appear incorrect when loaded") | 
|  | .with_help(format!( | 
|  | "to see what rules are missing, call `rustdoc --check-theme \"{theme_s}\"`", | 
|  | )) | 
|  | .emit(); | 
|  | } | 
|  | themes.push(StylePath { path: theme_file }); | 
|  | } | 
|  | } | 
|  |  | 
|  | let edition = config::parse_crate_edition(early_dcx, matches); | 
|  |  | 
|  | let mut id_map = html::markdown::IdMap::new(); | 
|  | let Some(external_html) = ExternalHtml::load( | 
|  | &matches.opt_strs("html-in-header"), | 
|  | &matches.opt_strs("html-before-content"), | 
|  | &matches.opt_strs("html-after-content"), | 
|  | &matches.opt_strs("markdown-before-content"), | 
|  | &matches.opt_strs("markdown-after-content"), | 
|  | nightly_options::match_is_nightly_build(matches), | 
|  | dcx, | 
|  | &mut id_map, | 
|  | edition, | 
|  | &None, | 
|  | ) else { | 
|  | dcx.fatal("`ExternalHtml::load` failed"); | 
|  | }; | 
|  |  | 
|  | match matches.opt_str("r").as_deref() { | 
|  | Some("rust") | None => {} | 
|  | Some(s) => dcx.fatal(format!("unknown input format: {s}")), | 
|  | } | 
|  |  | 
|  | let index_page = matches.opt_str("index-page").map(|s| PathBuf::from(&s)); | 
|  | if let Some(ref index_page) = index_page | 
|  | && !index_page.is_file() | 
|  | { | 
|  | dcx.fatal("option `--index-page` argument must be a file"); | 
|  | } | 
|  |  | 
|  | let target = parse_target_triple(early_dcx, matches); | 
|  | let sysroot = Sysroot::new(matches.opt_str("sysroot").map(PathBuf::from)); | 
|  |  | 
|  | let libs = matches | 
|  | .opt_strs("L") | 
|  | .iter() | 
|  | .map(|s| { | 
|  | SearchPath::from_cli_opt( | 
|  | sysroot.path(), | 
|  | &target, | 
|  | early_dcx, | 
|  | s, | 
|  | #[allow(rustc::bad_opt_access)] // we have no `Session` here | 
|  | unstable_opts.unstable_options, | 
|  | ) | 
|  | }) | 
|  | .collect(); | 
|  |  | 
|  | let crate_types = match parse_crate_types_from_list(matches.opt_strs("crate-type")) { | 
|  | Ok(types) => types, | 
|  | Err(e) => { | 
|  | dcx.fatal(format!("unknown crate type: {e}")); | 
|  | } | 
|  | }; | 
|  |  | 
|  | let crate_name = matches.opt_str("crate-name"); | 
|  | let bin_crate = crate_types.contains(&CrateType::Executable); | 
|  | let proc_macro_crate = crate_types.contains(&CrateType::ProcMacro); | 
|  | let playground_url = matches.opt_str("playground-url"); | 
|  | let module_sorting = if matches.opt_present("sort-modules-by-appearance") { | 
|  | ModuleSorting::DeclarationOrder | 
|  | } else { | 
|  | ModuleSorting::Alphabetical | 
|  | }; | 
|  | let resource_suffix = matches.opt_str("resource-suffix").unwrap_or_default(); | 
|  | let markdown_no_toc = matches.opt_present("markdown-no-toc"); | 
|  | let markdown_css = matches.opt_strs("markdown-css"); | 
|  | let markdown_playground_url = matches.opt_str("markdown-playground-url"); | 
|  | let crate_version = matches.opt_str("crate-version"); | 
|  | let enable_index_page = matches.opt_present("enable-index-page") || index_page.is_some(); | 
|  | let static_root_path = matches.opt_str("static-root-path"); | 
|  | let test_run_directory = matches.opt_str("test-run-directory").map(PathBuf::from); | 
|  | let persist_doctests = matches.opt_str("persist-doctests").map(PathBuf::from); | 
|  | let test_builder = matches.opt_str("test-builder").map(PathBuf::from); | 
|  | let codegen_options_strs = matches.opt_strs("C"); | 
|  | let unstable_opts_strs = matches.opt_strs("Z"); | 
|  | let lib_strs = matches.opt_strs("L"); | 
|  | let extern_strs = matches.opt_strs("extern"); | 
|  | let test_runtool = matches.opt_str("test-runtool"); | 
|  | let test_runtool_args = matches.opt_strs("test-runtool-arg"); | 
|  | let document_private = matches.opt_present("document-private-items"); | 
|  | let document_hidden = matches.opt_present("document-hidden-items"); | 
|  | let run_check = matches.opt_present("check"); | 
|  | let generate_redirect_map = matches.opt_present("generate-redirect-map"); | 
|  | let show_type_layout = matches.opt_present("show-type-layout"); | 
|  | let nocapture = matches.opt_present("nocapture"); | 
|  | let generate_link_to_definition = matches.opt_present("generate-link-to-definition"); | 
|  | let extern_html_root_takes_precedence = | 
|  | matches.opt_present("extern-html-root-takes-precedence"); | 
|  | let html_no_source = matches.opt_present("html-no-source"); | 
|  | let should_merge = match parse_merge(matches) { | 
|  | Ok(result) => result, | 
|  | Err(e) => dcx.fatal(format!("--merge option error: {e}")), | 
|  | }; | 
|  |  | 
|  | if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { | 
|  | dcx.struct_warn( | 
|  | "`--generate-link-to-definition` option can only be used with HTML output format", | 
|  | ) | 
|  | .with_note("`--generate-link-to-definition` option will be ignored") | 
|  | .emit(); | 
|  | } | 
|  |  | 
|  | let scrape_examples_options = ScrapeExamplesOptions::new(matches, dcx); | 
|  | let with_examples = matches.opt_strs("with-examples"); | 
|  | let call_locations = crate::scrape_examples::load_call_locations(with_examples, dcx); | 
|  | let doctest_build_args = matches.opt_strs("doctest-build-arg"); | 
|  |  | 
|  | let unstable_features = | 
|  | rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); | 
|  |  | 
|  | let disable_minification = matches.opt_present("disable-minification"); | 
|  |  | 
|  | let options = Options { | 
|  | bin_crate, | 
|  | proc_macro_crate, | 
|  | error_format, | 
|  | diagnostic_width, | 
|  | libs, | 
|  | lib_strs, | 
|  | externs, | 
|  | extern_strs, | 
|  | cfgs, | 
|  | check_cfgs, | 
|  | codegen_options, | 
|  | codegen_options_strs, | 
|  | unstable_opts, | 
|  | unstable_opts_strs, | 
|  | target, | 
|  | edition, | 
|  | sysroot, | 
|  | lint_opts, | 
|  | describe_lints, | 
|  | lint_cap, | 
|  | should_test, | 
|  | test_args, | 
|  | show_coverage, | 
|  | crate_version, | 
|  | test_run_directory, | 
|  | persist_doctests, | 
|  | test_runtool, | 
|  | test_runtool_args, | 
|  | test_builder, | 
|  | run_check, | 
|  | no_run, | 
|  | test_builder_wrappers, | 
|  | remap_path_prefix, | 
|  | nocapture, | 
|  | crate_name, | 
|  | output_format, | 
|  | json_unused_externs, | 
|  | scrape_examples_options, | 
|  | unstable_features, | 
|  | expanded_args: args, | 
|  | doctest_build_args, | 
|  | }; | 
|  | let render_options = RenderOptions { | 
|  | output, | 
|  | external_html, | 
|  | id_map, | 
|  | playground_url, | 
|  | module_sorting, | 
|  | themes, | 
|  | extension_css, | 
|  | extern_html_root_urls, | 
|  | extern_html_root_takes_precedence, | 
|  | default_settings, | 
|  | resource_suffix, | 
|  | enable_index_page, | 
|  | index_page, | 
|  | static_root_path, | 
|  | markdown_no_toc, | 
|  | markdown_css, | 
|  | markdown_playground_url, | 
|  | document_private, | 
|  | document_hidden, | 
|  | generate_redirect_map, | 
|  | show_type_layout, | 
|  | unstable_features, | 
|  | emit, | 
|  | generate_link_to_definition, | 
|  | call_locations, | 
|  | no_emit_shared: false, | 
|  | html_no_source, | 
|  | output_to_stdout, | 
|  | should_merge, | 
|  | include_parts_dir, | 
|  | parts_out_dir, | 
|  | disable_minification, | 
|  | }; | 
|  | Some((input, options, render_options)) | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns `true` if the file given as `self.input` is a Markdown file. | 
|  | pub(crate) fn markdown_input(input: &Input) -> Option<&Path> { | 
|  | input.opt_path().filter(|p| matches!(p.extension(), Some(e) if e == "md" || e == "markdown")) | 
|  | } | 
|  |  | 
|  | fn parse_remap_path_prefix( | 
|  | matches: &getopts::Matches, | 
|  | ) -> Result<Vec<(PathBuf, PathBuf)>, &'static str> { | 
|  | matches | 
|  | .opt_strs("remap-path-prefix") | 
|  | .into_iter() | 
|  | .map(|remap| { | 
|  | remap | 
|  | .rsplit_once('=') | 
|  | .ok_or("--remap-path-prefix must contain '=' between FROM and TO") | 
|  | .map(|(from, to)| (PathBuf::from(from), PathBuf::from(to))) | 
|  | }) | 
|  | .collect() | 
|  | } | 
|  |  | 
|  | /// Prints deprecation warnings for deprecated options | 
|  | fn check_deprecated_options(matches: &getopts::Matches, dcx: DiagCtxtHandle<'_>) { | 
|  | let deprecated_flags = []; | 
|  |  | 
|  | for &flag in deprecated_flags.iter() { | 
|  | if matches.opt_present(flag) { | 
|  | dcx.struct_warn(format!("the `{flag}` flag is deprecated")) | 
|  | .with_note( | 
|  | "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \ | 
|  | for more information", | 
|  | ) | 
|  | .emit(); | 
|  | } | 
|  | } | 
|  |  | 
|  | let removed_flags = ["plugins", "plugin-path", "no-defaults", "passes", "input-format"]; | 
|  |  | 
|  | for &flag in removed_flags.iter() { | 
|  | if matches.opt_present(flag) { | 
|  | let mut err = dcx.struct_warn(format!("the `{flag}` flag no longer functions")); | 
|  | err.note( | 
|  | "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \ | 
|  | for more information", | 
|  | ); | 
|  |  | 
|  | if flag == "no-defaults" || flag == "passes" { | 
|  | err.help("you may want to use --document-private-items"); | 
|  | } else if flag == "plugins" || flag == "plugin-path" { | 
|  | err.warn("see CVE-2018-1000622"); | 
|  | } | 
|  |  | 
|  | err.emit(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Extracts `--extern-html-root-url` arguments from `matches` and returns a map of crate names to | 
|  | /// the given URLs. If an `--extern-html-root-url` argument was ill-formed, returns an error | 
|  | /// describing the issue. | 
|  | fn parse_extern_html_roots( | 
|  | matches: &getopts::Matches, | 
|  | ) -> Result<BTreeMap<String, String>, &'static str> { | 
|  | let mut externs = BTreeMap::new(); | 
|  | for arg in &matches.opt_strs("extern-html-root-url") { | 
|  | let (name, url) = | 
|  | arg.split_once('=').ok_or("--extern-html-root-url must be of the form name=url")?; | 
|  | externs.insert(name.to_string(), url.to_string()); | 
|  | } | 
|  | Ok(externs) | 
|  | } | 
|  |  | 
|  | /// Path directly to crate-info file. | 
|  | /// | 
|  | /// For example, `/home/user/project/target/doc.parts/<crate>/crate-info`. | 
|  | #[derive(Clone, Debug)] | 
|  | pub(crate) struct PathToParts(pub(crate) PathBuf); | 
|  |  | 
|  | impl PathToParts { | 
|  | fn from_flag(path: String) -> Result<PathToParts, String> { | 
|  | let mut path = PathBuf::from(path); | 
|  | // check here is for diagnostics | 
|  | if path.exists() && !path.is_dir() { | 
|  | Err(format!( | 
|  | "--parts-out-dir and --include-parts-dir expect directories, found: {}", | 
|  | path.display(), | 
|  | )) | 
|  | } else { | 
|  | // if it doesn't exist, we'll create it. worry about that in write_shared | 
|  | path.push("crate-info"); | 
|  | Ok(PathToParts(path)) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Reports error if --include-parts-dir / crate-info is not a file | 
|  | fn parse_include_parts_dir(m: &getopts::Matches) -> Result<Vec<PathToParts>, String> { | 
|  | let mut ret = Vec::new(); | 
|  | for p in m.opt_strs("include-parts-dir") { | 
|  | let p = PathToParts::from_flag(p)?; | 
|  | // this is just for diagnostic | 
|  | if !p.0.is_file() { | 
|  | return Err(format!("--include-parts-dir expected {} to be a file", p.0.display())); | 
|  | } | 
|  | ret.push(p); | 
|  | } | 
|  | Ok(ret) | 
|  | } | 
|  |  | 
|  | /// Controls merging of cross-crate information | 
|  | #[derive(Debug, Clone)] | 
|  | pub(crate) struct ShouldMerge { | 
|  | /// Should we append to existing cci in the doc root | 
|  | pub(crate) read_rendered_cci: bool, | 
|  | /// Should we write cci to the doc root | 
|  | pub(crate) write_rendered_cci: bool, | 
|  | } | 
|  |  | 
|  | /// Extracts read_rendered_cci and write_rendered_cci from command line arguments, or | 
|  | /// reports an error if an invalid option was provided | 
|  | fn parse_merge(m: &getopts::Matches) -> Result<ShouldMerge, &'static str> { | 
|  | match m.opt_str("merge").as_deref() { | 
|  | // default = read-write | 
|  | None => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }), | 
|  | Some("none") if m.opt_present("include-parts-dir") => { | 
|  | Err("--include-parts-dir not allowed if --merge=none") | 
|  | } | 
|  | Some("none") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: false }), | 
|  | Some("shared") if m.opt_present("parts-out-dir") || m.opt_present("include-parts-dir") => { | 
|  | Err("--parts-out-dir and --include-parts-dir not allowed if --merge=shared") | 
|  | } | 
|  | Some("shared") => Ok(ShouldMerge { read_rendered_cci: true, write_rendered_cci: true }), | 
|  | Some("finalize") if m.opt_present("parts-out-dir") => { | 
|  | Err("--parts-out-dir not allowed if --merge=finalize") | 
|  | } | 
|  | Some("finalize") => Ok(ShouldMerge { read_rendered_cci: false, write_rendered_cci: true }), | 
|  | Some(_) => Err("argument to --merge must be `none`, `shared`, or `finalize`"), | 
|  | } | 
|  | } |