diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25bfd5b..46bd6e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,12 +20,17 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - name: Install rust + - name: Install rust (stable) uses: dtolnay/rust-toolchain@stable - with: - # We need to install the source of the standard library for the integration tests to check that links - # to the standard library are correctly generated. - components: rust-src, rustfmt + + # We need to install rust nightly to run the integration tests: nightly is needed to get the rustdoc information + # for intralinks. This will not be needed once that feature stabilizes + # (https://github.com/rust-lang/rust/issues/76578). + - name: Install rust (nightly) + uses: dtolnay/rust-toolchain@nightly + + - name: Use stable rust for the build + run: rustup default stable - name: Install cargo plugins run: | diff --git a/Cargo.lock b/Cargo.lock index da417b3..3476232 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,17 @@ dependencies = [ "serde", ] +[[package]] +name = "cargo-manifest" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db7ad32d2729eca70d1669bae38b6a29dabc30a16f1892cc00977873f450d0a" +dependencies = [ + "serde", + "thiserror", + "toml", +] + [[package]] name = "cargo-platform" version = "0.1.8" @@ -85,8 +96,11 @@ dependencies = [ "indoc", "itertools", "pretty_assertions", - "proc-macro2", "pulldown-cmark", + "rustdoc-json", + "rustdoc-types", + "serde", + "serde_json", "syn", "termcolor", "thiserror", @@ -158,17 +172,6 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" -[[package]] -name = "displaydoc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "either" version = "1.12.0" @@ -201,9 +204,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags", "libc", @@ -218,134 +221,14 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "idna" -version = "1.0.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -402,9 +285,9 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libgit2-sys" -version = "0.16.2+1.7.2" +version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" dependencies = [ "cc", "libc", @@ -424,12 +307,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - [[package]] name = "log" version = "0.4.21" @@ -438,9 +315,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "once_cell" @@ -454,6 +331,12 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + [[package]] name = "pkg-config" version = "0.3.30" @@ -507,6 +390,29 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rustdoc-json" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02afaf2615d8562f76c2a98b61c3d577304dc6429ea8ebaf654b3249e9197ac3" +dependencies = [ + "cargo-manifest", + "cargo_metadata", + "serde", + "thiserror", + "toml", + "tracing", +] + +[[package]] +name = "rustdoc-types" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9be1bc4a0ec3445cfa2e4ba112827544890d43d68b7d1eda5359a9c09d2cd8" +dependencies = [ + "serde", +] + [[package]] name = "ryu" version = "1.0.18" @@ -562,18 +468,6 @@ dependencies = [ "serde", ] -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "strsim" version = "0.11.1" @@ -591,17 +485,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "termcolor" version = "1.4.1" @@ -632,21 +515,27 @@ dependencies = [ ] [[package]] -name = "tinystr" -version = "0.7.6" +name = "tinyvec" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ - "displaydoc", - "zerovec", + "tinyvec_macros", ] +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "toml" version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -675,6 +564,37 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + [[package]] name = "unicase" version = "2.7.0" @@ -684,12 +604,27 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.13" @@ -698,27 +633,15 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "url" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -828,87 +751,8 @@ dependencies = [ "memchr", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "yansi" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerovec" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/Cargo.toml b/Cargo.toml index 4fd9c2e..bcb340f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,16 +38,19 @@ codecov = { repository = "orium/cargo-rdme", branch = "main", service = "github" toml = "0.8.14" thiserror = "1.0.61" syn = { version = "2.0.66", features = ["full", "extra-traits"] } -proc-macro2 = { version = "1.0.85", features = ["span-locations"] } pulldown-cmark = "0.11.0" itertools = "0.13.0" clap = "4.5.7" # Disable ssh support in git2 to avoid depending on openssl (which fails to build if an unsupported version is found). -git2 = { version = "0.18.3", default-features = false } +git2 = { version = "0.19.0", default-features = false } termcolor = "1.4.1" cargo_metadata = "0.18.1" indoc = "2.0.5" unicase = "2.7.0" +rustdoc-json = "0.9.1" +serde = "1.0.203" +serde_json = "1.0.117" +rustdoc-types = "0.26.0" [dev-dependencies] pretty_assertions = "1.4.0" diff --git a/src/console.rs b/src/console.rs index e1a13bf..aa8cec6 100644 --- a/src/console.rs +++ b/src/console.rs @@ -11,7 +11,7 @@ use termcolor::{ColorSpec, StandardStream}; pub use termcolor::Color; -fn is_stderr_terminal() -> bool { +pub fn is_stderr_terminal() -> bool { std::io::stderr().is_terminal() } diff --git a/src/lib.rs b/src/lib.rs index e56ce8d..7505000 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,9 +46,16 @@ pub fn find_first_file_in_ancestors(dir_path: impl AsRef, filename: &str) None } +#[derive(Debug)] +pub enum PackageTarget { + Bin { crate_name: String }, + Lib, +} + #[derive(PartialEq, Eq, Debug)] pub struct Project { package_name: String, + manifest_path: PathBuf, readme_path: Option, lib_path: Option, bin_path: HashMap, @@ -103,16 +110,16 @@ impl Project { let bin_packages = package.targets.iter().filter(|target| target.kind.contains(&"bin".to_owned())); - let directory = package - .manifest_path - .clone() - .into_std_path_buf() + let manifest_path = package.manifest_path.clone().into_std_path_buf(); + + let directory = manifest_path .parent() .expect("error getting the parent path of the manifest file") .to_path_buf(); Project { package_name: package.name.clone(), + manifest_path, readme_path: package.readme.as_ref().map(|p| p.clone().into_std_path_buf()), lib_path: lib_package.map(|t| t.src_path.clone().into_std_path_buf()), bin_path: bin_packages @@ -127,6 +134,14 @@ impl Project { self.lib_path.as_ref().filter(|p| p.is_file()).map(PathBuf::as_path) } + #[must_use] + pub fn get_bin_default_crate_name(&self) -> Option<&str> { + match self.bin_path.len() { + 1 => self.bin_path.keys().next().map(String::as_str), + _ => None, + } + } + #[must_use] pub fn get_bin_default_entryfile_path(&self) -> Option<&Path> { match self.bin_path.len() { @@ -157,15 +172,11 @@ impl Project { pub fn get_package_name(&self) -> &str { &self.package_name } -} -fn project_package_name(manifest_path: impl AsRef) -> Option { - let str: String = std::fs::read_to_string(&manifest_path).ok()?; - let toml: toml::Value = toml::from_str(&str).ok()?; - let package_name = - toml.get("package").and_then(|v| v.get("name")).and_then(toml::Value::as_str)?; - - Some(package_name.to_owned()) + #[must_use] + pub fn get_manifest_path(&self) -> &PathBuf { + &self.manifest_path + } } #[derive(Eq, PartialEq, Clone, Debug)] diff --git a/src/main.rs b/src/main.rs index 68c42e9..488ee52 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ */ #![cfg_attr(feature = "fatal-warnings", deny(warnings))] +// WIP! update README // Note: If you change this remember to update `README.md`. To do so run `cargo run`. //! Cargo command to create your README from your crate’s documentation. //! @@ -221,7 +222,7 @@ use crate::options::{EntrypointOpt, LineTerminatorOpt}; use cargo_rdme::transform::IntralinkError; use cargo_rdme::{ extract_doc_from_source_file, infer_line_terminator, inject_doc_in_readme, LineTerminator, - Project, + PackageTarget, Project, }; use cargo_rdme::{Doc, ProjectError, Readme}; use std::cell::Cell; @@ -250,6 +251,7 @@ impl From for ExitCode { | RunError::ExtractDocError(_) | RunError::ReadmeError(_) | RunError::NoEntrySourceFile + | RunError::NoTargetPackage | RunError::NoReadmeFile | RunError::NoRustdoc | RunError::InjectDocError(_) @@ -272,8 +274,10 @@ enum RunError { ExtractDocError(cargo_rdme::ExtractDocError), #[error("failed to process README: {0}")] ReadmeError(cargo_rdme::ReadmeError), - #[error("failed get crate's entry source file")] + #[error("failed to get crate's entry source file")] NoEntrySourceFile, + #[error("failed to get crate's target package")] + NoTargetPackage, #[error("crate's README file not found")] NoReadmeFile, #[error("crate-level rustdoc not found")] @@ -365,6 +369,23 @@ fn entrypoint<'a>(project: &'a Project, entrypoint_opt: &EntrypointOpt) -> Optio } } +fn package_target(project: &Project, entrypoint_opt: &EntrypointOpt) -> Option { + let bin_default = || { + project + .get_bin_default_crate_name() + .map(|name| PackageTarget::Bin { crate_name: name.to_owned() }) + }; + + match entrypoint_opt { + EntrypointOpt::Auto => { + project.get_lib_entryfile_path().map(|_| PackageTarget::Lib).or_else(bin_default) + } + EntrypointOpt::Lib => Some(PackageTarget::Lib), + EntrypointOpt::BinDefault => bin_default(), + EntrypointOpt::BinName(name) => Some(PackageTarget::Bin { crate_name: name.clone() }), + } +} + fn line_terminator( line_terminator_opt: LineTerminatorOpt, readme_path: impl AsRef, @@ -383,7 +404,7 @@ struct Warnings { fn transform_doc( doc: &Doc, project: &Project, - entrypoint: impl AsRef, + package_target: PackageTarget, options: &options::Options, ) -> Result<(Doc, Warnings), RunError> { use cargo_rdme::transform::{ @@ -402,7 +423,9 @@ fn transform_doc( let had_warnings = Cell::new(false); let transform = DocTransformIntralinks::new( project.get_package_name(), - entrypoint, + package_target, + options.workspace_project.clone(), + project.get_manifest_path().clone(), |msg| { print_warning!("{}", msg); had_warnings.set(true); @@ -448,12 +471,14 @@ fn run(options: options::Options) -> Result<(), RunError> { }; let entryfile: &Path = entrypoint(&project, &options.entrypoint).ok_or(RunError::NoEntrySourceFile)?; + let package_target: PackageTarget = + package_target(&project, &options.entrypoint).ok_or(RunError::NoTargetPackage)?; let doc: Doc = match extract_doc_from_source_file(entryfile)? { None => return Err(RunError::NoRustdoc), Some(doc) => doc, }; - let (doc, warnings) = transform_doc(&doc, &project, entryfile, &options)?; + let (doc, warnings) = transform_doc(&doc, &project, package_target, &options)?; let readme_path: PathBuf = match options.readme_path { None => project.get_readme_path().ok_or(RunError::NoReadmeFile)?, diff --git a/src/transform/intralinks/links.rs b/src/transform/intralinks/links.rs index 5924a23..7580cc2 100644 --- a/src/transform/intralinks/links.rs +++ b/src/transform/intralinks/links.rs @@ -4,7 +4,6 @@ */ use crate::markdown::Markdown; -use crate::transform::intralinks::ItemPath; use crate::utils::{MarkdownItemIterator, Span}; use itertools::Itertools; use pulldown_cmark::{CowStr, TagEnd}; @@ -72,16 +71,14 @@ impl Display for MarkdownInlineLink { } } -#[derive(Eq, PartialEq, Clone, Debug)] +#[derive(Eq, PartialEq, Hash, Clone, Debug)] pub struct Link { pub raw_link: String, } impl Link { - pub fn link_as_item_path(&self) -> Option { - let link = self.split_link_fragment().0; - - ItemPath::from_string(link) + pub fn new(raw_link: String) -> Link { + Link { raw_link } } fn split_link_fragment(&self) -> (&str, &str) { @@ -101,12 +98,16 @@ impl Link { let (l, f) = link.split_at(i); ( strip_last_backtick(strip_backtick_end, l), - strip_last_backtick(strip_backtick_end, f), + strip_last_backtick(strip_backtick_end, &f[1..]), ) } } } + pub fn symbol(&self) -> &str { + self.split_link_fragment().0 + } + pub fn link_fragment(&self) -> Option<&str> { match self.split_link_fragment().1 { "" => None, @@ -117,13 +118,13 @@ impl Link { impl From for Link { fn from(raw_link: String) -> Link { - Link { raw_link } + Link::new(raw_link) } } impl From<&str> for Link { fn from(raw_link: &str) -> Link { - Link { raw_link: raw_link.to_owned() } + Link::new(raw_link.to_owned()) } } @@ -135,7 +136,9 @@ impl Display for Link { #[derive(Eq, PartialEq, Clone, Debug)] pub enum MarkdownReferenceLink { + /// A reference link like [text][label]. Normal { text: String, label: UniCase }, + /// A reference link like [label]. Note that the label is also the text. Shortcut { text: UniCase }, } @@ -177,8 +180,7 @@ pub fn markdown_link_iterator(markdown: &Markdown) -> MarkdownItemIterator MarkdownItemIterator None, - None => None, + Some(_) | None => None, }, _ => { if in_link.is_some() { diff --git a/src/transform/intralinks/mod.rs b/src/transform/intralinks/mod.rs index dab3756..2757524 100644 --- a/src/transform/intralinks/mod.rs +++ b/src/transform/intralinks/mod.rs @@ -6,41 +6,38 @@ use crate::transform::intralinks::links::{ markdown_link_iterator, markdown_reference_link_definition_iterator, Link, MarkdownLink, }; +use crate::transform::intralinks::rustdoc::{create_intralink_resolver, IntralinkResolver}; use crate::transform::DocTransform; -use crate::Doc; -use module_walker::walk_module_file; -use std::collections::{HashMap, HashSet}; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::path::{Path, PathBuf}; -use std::rc::Rc; -use syn::{Item, ItemMod}; +use crate::{Doc, PackageTarget}; +use itertools::Itertools; +use std::borrow::Cow; +use std::collections::HashSet; +use std::fmt::Display; +use std::path::PathBuf; use thiserror::Error; use unicase::UniCase; mod links; -mod module_walker; +mod rustdoc; #[derive(Error, Debug)] pub enum IntralinkError { - #[error("IO error: {0}")] - IOError(std::io::Error), - #[error("failed to analyzing code: {0}")] - AstWalkError(module_walker::ModuleWalkError), - #[error("failed to load standard library: {0}")] - LoadStdLibError(String), -} - -impl From for IntralinkError { - fn from(err: std::io::Error) -> Self { - IntralinkError::IOError(err) - } -} - -impl From for IntralinkError { - fn from(err: module_walker::ModuleWalkError) -> Self { - IntralinkError::AstWalkError(err) - } + #[error("failed to run rustdoc: {error}")] + RustdocError { + #[source] + error: rustdoc_json::BuildError, + }, + #[error("failed to run rustdoc:\n{stderr}")] + BuildRustdocError { stderr: String }, + #[error("failed to read rustdoc json file: {io_error}")] + ReadRustdocError { + #[source] + io_error: std::io::Error, + }, + #[error("failed to parse rustdoc json file")] + ParseRustdocError, + #[error("unsupported rustdoc format version {version} (expected version {expected_version})")] + UnsupportedRustdocFormatVersion { version: u32, expected_version: u32 }, } #[derive(Default, Debug, PartialEq, Eq, Clone)] @@ -56,8 +53,10 @@ pub struct IntralinksConfig { } pub struct DocTransformIntralinks { - crate_name: String, - entrypoint: PathBuf, + package_name: String, + package_target: PackageTarget, + workspace_package: Option, + manifest_path: PathBuf, emit_warning: F, config: IntralinksConfig, } @@ -67,189 +66,59 @@ where F: Fn(&str), { pub fn new( - crate_name: impl Into, - entrypoint: impl AsRef, + package_name: impl Into, + package_target: PackageTarget, + workspace_package: Option, + manifest_path: PathBuf, emit_warning: F, config: Option, ) -> DocTransformIntralinks { DocTransformIntralinks { - crate_name: crate_name.into(), - entrypoint: entrypoint.as_ref().to_path_buf(), + package_name: package_name.into(), + package_target, + workspace_package, + manifest_path, emit_warning, config: config.unwrap_or_default(), } } } -impl DocTransform for DocTransformIntralinks -where - F: Fn(&str), -{ - type E = IntralinkError; - - fn transform(&self, doc: &Doc) -> Result { - let symbols: HashSet = extract_markdown_intralink_symbols(doc); - - // If there are no intralinks in the doc don't even bother doing anything else. - if symbols.is_empty() { - return Ok(doc.clone()); - } - - // We only load symbols type information when we need them. - let symbols_type = match self.config.strip_links.unwrap_or(false) { - false => load_symbols_type(&self.entrypoint, &symbols, &self.emit_warning)?, - true => HashMap::new(), - }; - - let doc = - rewrite_links(doc, &symbols_type, &self.crate_name, &self.emit_warning, &self.config); - - Ok(doc) - } -} - -fn rewrite_links( - doc: &Doc, - symbols_type: &HashMap, - crate_name: &str, - emit_warning: &impl Fn(&str), - config: &IntralinksConfig, -) -> Doc { - let RewriteReferenceLinksResult { doc, reference_links_to_remove } = - rewrite_reference_links_definitions(doc, symbols_type, crate_name, emit_warning, config); - - let doc = rewrite_markdown_links( - &doc, - symbols_type, - crate_name, - emit_warning, - config, - &reference_links_to_remove, - ); - - // TODO Refactor link removal code so that it all happens in a new phase and not inside the - // functions above. - - doc -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] -pub enum ItemPathAnchor { - /// The anchor of a path starting with `::` such as `::std::fs::read`. - Root, - /// The anchor of a path starting with `crate` such as `crate::foo::is_prime`. - Crate, +#[derive(PartialEq, Eq, Hash, Clone, Debug)] +struct ItemPath<'a> { + segments: Cow<'a, [String]>, } -/// The rust path of an item, such as `foo::bar::is_prime` or `crate`. -#[derive(Clone)] -pub struct ItemPath { - pub anchor: ItemPathAnchor, +impl<'a> ItemPath<'a> { + fn new(segments: &'a [String]) -> ItemPath<'a> { + assert!(!segments.is_empty(), "path item must not be empty"); - /// This path vector can be shared and can end after the `item_path` we are representing. - /// This allows us to have a faster implementation for `ItemPath::all_ancestors()`. - path_shared: Rc>, - path_end: usize, -} - -impl ItemPath { - fn new(anchor: ItemPathAnchor) -> ItemPath { - ItemPath { anchor, path_shared: Rc::new(Vec::new()), path_end: 0 } + ItemPath { segments: Cow::Borrowed(segments) } } - fn root(crate_name: &str) -> ItemPath { - ItemPath::new(ItemPathAnchor::Root).join(&crate_name) - } + fn add(&self, segment: String) -> ItemPath<'static> { + let mut segments = self.segments.clone().into_owned(); - fn from_string(s: &str) -> Option { - let anchor; - let rest; - - if let Some(r) = s.strip_prefix("::") { - anchor = ItemPathAnchor::Root; - rest = r; - } else if s == "crate" { - return Some(ItemPath::new(ItemPathAnchor::Crate)); - } else if let Some(r) = s.strip_prefix("crate::") { - anchor = ItemPathAnchor::Crate; - rest = r; - } else { - return None; - } - - if rest.is_empty() { - return None; - } + segments.push(segment); - let path: Rc> = Rc::new(rest.split("::").map(str::to_owned).collect()); - - Some(ItemPath { anchor, path_end: path.len(), path_shared: path }) + ItemPath { segments: Cow::Owned(segments) } } - fn path_components(&self) -> impl Iterator { - self.path_shared[0..self.path_end].iter().map(String::as_str) + fn segments(&self) -> impl Iterator { + self.segments.iter().map(String::as_str) } - fn is_toplevel(&self) -> bool { - match self.anchor { - ItemPathAnchor::Root => self.path_end <= 1, - ItemPathAnchor::Crate => self.path_end == 0, - } - } - - fn parent(mut self) -> Option { - match self.is_toplevel() { - true => None, - false => { - self.path_end -= 1; - Some(self) - } - } - } - - fn name(&self) -> Option<&str> { - self.path_end.checked_sub(1).and_then(|i| self.path_shared.get(i)).map(String::as_str) - } - - fn join(mut self, s: &impl ToString) -> ItemPath { - let path = Rc::make_mut(&mut self.path_shared); - path.truncate(self.path_end); - path.push(s.to_string()); - self.path_end += 1; - self - } - - fn all_ancestors(&self) -> impl Iterator { - let first_ancestor = self.clone().parent(); - - std::iter::successors(first_ancestor, |ancestor| ancestor.clone().parent()) - } -} - -impl PartialEq for ItemPath { - fn eq(&self, other: &Self) -> bool { - self.anchor == other.anchor && self.path_components().eq(other.path_components()) + fn len(&self) -> usize { + self.segments.len() } } -impl Eq for ItemPath {} - -impl Hash for ItemPath { - fn hash(&self, state: &mut H) { - self.anchor.hash(state); - self.path_components().for_each(|c| c.hash(state)); - } -} - -impl fmt::Display for ItemPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self.anchor { - ItemPathAnchor::Root => (), - ItemPathAnchor::Crate => f.write_str("crate")?, - } +impl Display for ItemPath<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + // TODO Use standard library intersperse() one it stabilizes (https://github.com/rust-lang/rust/issues/79524). + let iter = Itertools::intersperse(self.segments.iter().map(String::as_str), "::"); - for s in self.path_components() { - f.write_str("::")?; + for s in iter { f.write_str(s)?; } @@ -257,377 +126,83 @@ impl fmt::Display for ItemPath { } } -impl fmt::Debug for ItemPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt::Display::fmt(self, f) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ImplSymbolType { - Method, - Const, - Type, -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum SymbolType { - Crate, - Struct, - Trait, - Enum, - Union, - Type, - Mod, - Macro, - Const, - Fn, - Static, - ImplItem(ImplSymbolType), -} - -impl SymbolType { - /// Returns the path of the module where this item is defined. - /// - /// Importantly, if inse module `crate::amod` we have a `struct Foo` with method `Foo::method()`, - /// this will `Foo::method()` return `crate::amod`. - fn get_module_path(self, path: &ItemPath) -> Option { - match self { - SymbolType::Crate => { - assert!(path.is_toplevel(), "a crate should always be in a toplevel path"); - None - } - SymbolType::Struct - | SymbolType::Trait - | SymbolType::Enum - | SymbolType::Union - | SymbolType::Type - | SymbolType::Mod - | SymbolType::Macro - | SymbolType::Const - | SymbolType::Fn - | SymbolType::Static => { - let p = path.clone().parent().unwrap_or_else(|| { - panic!("item {path} of type {self:?} should have a parent module") - }); - Some(p) - } - SymbolType::ImplItem(_) => { - let p = path - .clone() - .parent() - .unwrap_or_else(|| { - panic!("item {path} of type {self:?} should have a parent type") - }) - .parent() - .unwrap_or_else(|| { - panic!("item {path} of type {self:?} should have a parent module") - }); - Some(p) - } - } - } -} - -fn symbols_type_impl_block( - module: &ItemPath, - impl_block: &syn::ItemImpl, -) -> Vec<(ItemPath, SymbolType)> { - use syn::{ImplItem, Type, TypePath}; - - if let Type::Path(TypePath { qself: None, path }) = &*impl_block.self_ty { - if let Some(self_ident) = path.get_ident().map(ToString::to_string) { - let self_path = module.clone().join(&self_ident); - - return impl_block - .items - .iter() - .filter_map(|item| match item { - ImplItem::Fn(m) => { - let ident = m.sig.ident.to_string(); - - Some((ident, ImplSymbolType::Method)) - } - ImplItem::Const(c) => { - let ident = c.ident.to_string(); - - Some((ident, ImplSymbolType::Const)) - } - ImplItem::Type(t) => { - let ident = t.ident.to_string(); - - Some((ident, ImplSymbolType::Type)) - } - _ => None, - }) - .map(|(ident, tpy)| (self_path.clone().join(&ident), SymbolType::ImplItem(tpy))) - .collect(); - } - } - - Vec::new() -} - -fn item_symbols_type(module: &ItemPath, item: &Item) -> Vec<(ItemPath, SymbolType)> { - let item_path = |ident: &syn::Ident| module.clone().join(ident); - - let (path, symbol_type) = match item { - Item::Enum(e) => (item_path(&e.ident), SymbolType::Enum), - Item::Struct(s) => (item_path(&s.ident), SymbolType::Struct), - Item::Trait(t) => (item_path(&t.ident), SymbolType::Trait), - Item::Union(u) => (item_path(&u.ident), SymbolType::Union), - Item::Type(t) => (item_path(&t.ident), SymbolType::Type), - Item::Mod(m) => (item_path(&m.ident), SymbolType::Mod), - Item::Macro(syn::ItemMacro { ident: Some(ident), .. }) => { - (item_path(ident), SymbolType::Macro) - } - Item::Const(c) => (item_path(&c.ident), SymbolType::Const), - Item::Fn(f) => (item_path(&f.sig.ident), SymbolType::Fn), - Item::Static(s) => (item_path(&s.ident), SymbolType::Static), - Item::Impl(impl_block) => { - return symbols_type_impl_block(module, impl_block); - } - - _ => return Vec::new(), - }; +fn has_intralinks(doc: &Doc) -> bool { + let inline_links = + markdown_link_iterator(&doc.markdown).items().filter_map(|link| match link { + MarkdownLink::Inline { link } => Some(link.link), + MarkdownLink::Reference { .. } => None, + }); + let reference_links = markdown_reference_link_definition_iterator(&doc.markdown) + .items() + .map(|link_def| link_def.link); - vec![(path, symbol_type)] + inline_links.chain(reference_links).any(|link| IntralinkResolver::is_intralink(&link)) } -fn is_cfg_test(attribute: &syn::Attribute) -> bool { - let test_attribute: syn::Attribute = syn::parse_quote!(#[cfg(test)]); - - *attribute == test_attribute -} +impl DocTransform for DocTransformIntralinks +where + F: Fn(&str), +{ + type E = IntralinkError; -fn visit_module_item( - save_symbol: impl Fn(&ItemPath) -> bool, - symbols_type: &mut HashMap, - module: &ItemPath, - item: &Item, -) { - for (symbol, symbol_type) in item_symbols_type(module, item) { - if save_symbol(&symbol) { - symbols_type.insert(symbol, symbol_type); + fn transform(&self, doc: &Doc) -> Result { + // If there are no intralinks we don't want to require users to install the nightly + // toolchain and to have the performance penalty of running rustdoc. + if !has_intralinks(doc) { + return Ok(doc.clone()); } - } -} - -/// Returns whether we should explore a module. -fn check_explore_module( - should_explore_module: impl Fn(&ItemPath) -> bool, - modules_visited: &mut HashSet, - mod_symbol: &ItemPath, - mod_item: &ItemMod, -) -> bool { - // Conditional compilation can create multiple module definitions, e.g. - // - // ``` - // #[cfg(foo)] - // mod a {} - // #[cfg(not(foo))] - // mod a {} - // ``` - // - // We choose to consider the first one only. - if modules_visited.contains(mod_symbol) { - return false; - } - // If a module is gated by `#[cfg(test)]` we skip it. This happens sometimes in the - // standard library, and we want to explore the correct, non-test, module. - if mod_item.attrs.iter().any(is_cfg_test) { - return false; - } - - let explore = should_explore_module(mod_symbol); + let strip_links = self.config.strip_links.unwrap_or(false); - if explore { - modules_visited.insert(mod_symbol.clone()); - } - - explore -} + // WIP! have a thing that checks if the nightly toolchain is installed -fn explore_crate>( - file: P, - crate_symbol: &ItemPath, - symbols: &HashSet, - paths_to_explore: &HashSet, - symbols_type: &mut HashMap, - emit_warning: &impl Fn(&str), -) -> Result<(), module_walker::ModuleWalkError> { - let mut modules_visited: HashSet = HashSet::new(); - - // Walking the module only visits items, which means we need to add the root `crate` explicitly. - symbols_type.insert(crate_symbol.clone(), SymbolType::Crate); - - let mut visit = |module: &ItemPath, item: &Item| { - let save_symbol = |symbol: &ItemPath| { - // We also check if it belongs to the paths to explore because of impl items (e.g. a - // method `Foo::method` we need to know about `Foo` type. For instance if `Foo` is a - // struct then the link will be `⋯/struct.Foo.html#method.method`. - symbols.contains(symbol) || paths_to_explore.contains(symbol) + let intralink_resolver: IntralinkResolver<'_> = match strip_links { + true => { + // WIP! ideally we wouldn't create an intralink resolver if we dont need it + // see other WIP to split the strip links into another place. + IntralinkResolver::new(self.package_name.as_str(), &self.config.docs_rs) + } + // We only load symbols type information when we need them. + false => { + // WIP! ideally we would want to return + create_intralink_resolver( + self.package_name.as_str(), + &self.package_target, + self.workspace_package.as_deref(), + &self.manifest_path, + &self.config, + )? + } }; - visit_module_item(save_symbol, symbols_type, module, item); - }; - - let mut explore_module = |mod_symbol: &ItemPath, mod_item: &ItemMod| -> bool { - check_explore_module( - |mod_symbol| paths_to_explore.contains(mod_symbol), - &mut modules_visited, - mod_symbol, - mod_item, - ) - }; + let doc = rewrite_links(doc, &intralink_resolver, &self.emit_warning, &self.config); - walk_module_file(file, crate_symbol, &mut visit, &mut explore_module, emit_warning) + Ok(doc) + } } -fn load_symbols_type>( - entry_point: P, - symbols: &HashSet, +// WIP! separate the strip links logic from rewrite_reference_links_definitions and rewrite_markdown_links? +fn rewrite_links( + doc: &Doc, + intralink_resolver: &IntralinkResolver, emit_warning: &impl Fn(&str), -) -> Result, IntralinkError> { - let paths_to_explore: HashSet = all_ancestor_paths(symbols.iter()); - let mut symbols_type: HashMap = HashMap::new(); - - // Only load standard library information if needed. - let std_lib_crates = match references_standard_library(symbols) { - true => get_standard_libraries()?, - false => Vec::new(), - }; - - for Crate { name, entrypoint } in std_lib_crates { - explore_crate( - entrypoint, - &ItemPath::root(&name), - symbols, - &paths_to_explore, - &mut symbols_type, - emit_warning, - )?; - } + config: &IntralinksConfig, +) -> Doc { + let RewriteReferenceLinksResult { doc, reference_links_to_remove } = + rewrite_reference_links_definitions(doc, intralink_resolver, emit_warning, config); - explore_crate( - entry_point, - &ItemPath::new(ItemPathAnchor::Crate), - symbols, - &paths_to_explore, - &mut symbols_type, + let doc = rewrite_markdown_links( + &doc, + intralink_resolver, emit_warning, - )?; - - Ok(symbols_type) -} - -/// Create a set with all ancestor paths of `symbols`. For instance, if `symbols` is -/// `{crate::foo::bar::baz, crate::baz::mumble}` it will return -/// `{crate, crate::foo, crate::foo::bar, crate::baz}`. -fn all_ancestor_paths<'a>(symbols: impl Iterator) -> HashSet { - symbols.into_iter().flat_map(ItemPath::all_ancestors).collect() -} - -fn extract_markdown_intralink_symbols(doc: &Doc) -> HashSet { - let item_paths_inline_links = - markdown_link_iterator(&doc.markdown).items().filter_map(|l| match l { - MarkdownLink::Inline { link: inline_link } => inline_link.link.link_as_item_path(), - MarkdownLink::Reference { .. } => None, - }); - - let item_paths_reference_link_def = markdown_reference_link_definition_iterator(&doc.markdown) - .items() - .filter_map(|l| l.link.link_as_item_path()); - - item_paths_inline_links.chain(item_paths_reference_link_def).collect() -} - -/// Returns the url for the item. -/// -/// This returns `None` if the item type(s) was not successfully resolved. -fn documentation_url( - item_path: &ItemPath, - symbols_type: &HashMap, - crate_name: &str, - fragment: Option<&str>, - config: &IntralinksDocsRsConfig, -) -> Option { - let package_name = crate_name.replace('-', "_"); - let typ = *symbols_type.get(item_path)?; - - let mut link = match item_path.anchor { - ItemPathAnchor::Root => { - let std_crate_name = - item_path.path_components().next().expect("a root path should not be empty"); - format!("https://doc.rust-lang.org/stable/{std_crate_name}/") - } - ItemPathAnchor::Crate => { - let base_url = - config.docs_rs_base_url.as_ref().map_or("https://docs.rs", String::as_str); - let version = config.docs_rs_version.as_ref().map_or("latest", String::as_str); - - format!("{base_url}/{crate_name}/{version}/{package_name}/") - } - }; - - if typ == SymbolType::Crate { - return Some(format!("{}{}", link, fragment.unwrap_or(""))); - } - - let skip_components = match item_path.anchor { - ItemPathAnchor::Root => 1, - ItemPathAnchor::Crate => 0, - }; - - let module_path = typ.get_module_path(item_path).expect("item should belong to a module"); - - for s in module_path.path_components().skip(skip_components) { - link.push_str(s); - link.push('/'); - } - - let name = - item_path.name().unwrap_or_else(|| panic!("failed to get last component of {item_path}")); - - match typ { - SymbolType::Crate => unreachable!(), - SymbolType::Struct => link.push_str(&format!("struct.{name}.html")), - SymbolType::Trait => link.push_str(&format!("trait.{name}.html")), - SymbolType::Enum => link.push_str(&format!("enum.{name}.html")), - SymbolType::Union => link.push_str(&format!("union.{name}.html")), - SymbolType::Type => link.push_str(&format!("type.{name}.html")), - SymbolType::Mod => link.push_str(&format!("{name}/")), - SymbolType::Macro => link.push_str(&format!("macro.{name}.html")), - SymbolType::Const => link.push_str(&format!("const.{name}.html")), - SymbolType::Fn => link.push_str(&format!("fn.{name}.html")), - SymbolType::Static => link.push_str(&format!("static.{name}.html")), - SymbolType::ImplItem(typ) => { - let parent_path = item_path - .clone() - .parent() - .unwrap_or_else(|| panic!("item {item_path} should always have a parent")); - - let link = documentation_url( - &parent_path, - symbols_type, - crate_name, - // We discard the fragment. - None, - config, - )?; - - let impl_item_fragment_str = match typ { - ImplSymbolType::Method => "method", - ImplSymbolType::Const => "associatedconstant", - ImplSymbolType::Type => "associatedtype", - }; + config, + &reference_links_to_remove, + ); - return Some(format!("{link}#{impl_item_fragment_str}.{name}")); - } - } + // TODO Refactor link removal code so that it all happens in a new phase and not inside the + // functions above. - Some(format!("{}{}", link, fragment.unwrap_or(""))) + doc } enum MarkdownLinkAction { @@ -638,39 +213,31 @@ enum MarkdownLinkAction { fn markdown_link( link: &Link, - symbols_type: &HashMap, - crate_name: &str, + intralink_resolver: &IntralinkResolver, emit_warning: &impl Fn(&str), - config: &IntralinksConfig, ) -> MarkdownLinkAction { - match link.link_as_item_path() { - Some(symbol) => { - let link = documentation_url( - &symbol, - symbols_type, - crate_name, - link.link_fragment(), - &config.docs_rs, - ); - - match link { - Some(l) => MarkdownLinkAction::Link(l.into()), - None => { - emit_warning(&format!("Could not resolve definition of `{symbol}`.")); - - // This was an intralink, but we were not able to generate a link. - MarkdownLinkAction::Strip - } - } + assert!(IntralinkResolver::is_intralink(link)); + + match intralink_resolver.resolve_link(link) { + None => { + emit_warning(&format!("Could not resolve definition of `{}`.", link.symbol())); + + MarkdownLinkAction::Strip + } + Some(url) => { + let url = match link.link_fragment() { + None => url.to_owned(), + Some(fragment) => format!("{url}#{fragment}"), + }; + + MarkdownLinkAction::Link(url.into()) } - None => MarkdownLinkAction::Preserve, } } fn rewrite_markdown_links( doc: &Doc, - symbols_type: &HashMap, - crate_name: &str, + intralink_resolver: &IntralinkResolver, emit_warning: &impl Fn(&str), config: &IntralinksConfig, reference_links_to_remove: &HashSet>, @@ -683,19 +250,16 @@ fn rewrite_markdown_links( for item_or_other in markdown_link_iterator(&doc.markdown).complete() { match item_or_other { ItemOrOther::Item(MarkdownLink::Inline { link: inline_link }) => { - let markdown_link: MarkdownLinkAction = match strip_links { - false => markdown_link( - &inline_link.link, - symbols_type, - crate_name, - emit_warning, - config, - ), - true => match inline_link.link.link_as_item_path() { - None => MarkdownLinkAction::Preserve, - Some(_) => MarkdownLinkAction::Strip, - }, - }; + let markdown_link: MarkdownLinkAction = + match IntralinkResolver::is_intralink(&inline_link.link) { + true => match strip_links { + false => { + markdown_link(&inline_link.link, intralink_resolver, emit_warning) + } + true => MarkdownLinkAction::Strip, + }, + false => MarkdownLinkAction::Preserve, + }; match markdown_link { MarkdownLinkAction::Link(markdown_link) => { @@ -731,8 +295,7 @@ struct RewriteReferenceLinksResult { fn rewrite_reference_links_definitions( doc: &Doc, - symbols_type: &HashMap, - crate_name: &str, + intralink_resolver: &IntralinkResolver, emit_warning: &impl Fn(&str), config: &IntralinksConfig, ) -> RewriteReferenceLinksResult { @@ -747,19 +310,16 @@ fn rewrite_reference_links_definitions( for item_or_other in iter.complete() { match item_or_other { ItemOrOther::Item(link_ref_def) => { - let markdown_link: MarkdownLinkAction = match strip_links { - false => markdown_link( - &link_ref_def.link, - symbols_type, - crate_name, - emit_warning, - config, - ), - true => match link_ref_def.link.link_as_item_path() { - None => MarkdownLinkAction::Preserve, - Some(_) => MarkdownLinkAction::Strip, - }, - }; + let markdown_link: MarkdownLinkAction = + match IntralinkResolver::is_intralink(&link_ref_def.link) { + true => match strip_links { + false => { + markdown_link(&link_ref_def.link, intralink_resolver, emit_warning) + } + true => MarkdownLinkAction::Strip, + }, + false => MarkdownLinkAction::Preserve, + }; match markdown_link { MarkdownLinkAction::Link(link) => { @@ -799,322 +359,14 @@ fn rewrite_reference_links_definitions( RewriteReferenceLinksResult { doc: Doc::from_str(new_doc), reference_links_to_remove } } -fn get_rustc_sysroot_libraries_dir() -> Result { - use std::process::Command; - - let output = Command::new("rustc") - .args(["--print=sysroot"]) - .output() - .map_err(|e| IntralinkError::LoadStdLibError(format!("failed to run rustc: {e}")))?; - - let s = String::from_utf8(output.stdout).expect("unexpected output from rustc"); - let sysroot = PathBuf::from(s.trim()); - let src_path = sysroot.join("lib").join("rustlib").join("src").join("rust").join("library"); - - match src_path.is_dir() { - false => Err(IntralinkError::LoadStdLibError(format!( - "Cannot find rust standard library in \"{}\"", - src_path.display() - ))), - true => Ok(src_path), - } -} - -#[derive(Debug)] -struct Crate { - name: String, - entrypoint: PathBuf, -} - -fn references_standard_library(symbols: &HashSet) -> bool { - // The only way to reference standard libraries that we support is with a intra-link of form `::⋯`. - symbols.iter().any(|symbol| symbol.anchor == ItemPathAnchor::Root) -} - -fn get_standard_libraries() -> Result, IntralinkError> { - let libraries_dir = get_rustc_sysroot_libraries_dir()?; - let mut std_libs = Vec::with_capacity(64); - - for entry in std::fs::read_dir(libraries_dir)? { - let entry = entry?; - let project_dir_path = entry.path(); - let cargo_manifest_path = project_dir_path.join("Cargo.toml"); - let lib_entrypoint = project_dir_path.join("src").join("lib.rs"); - - if cargo_manifest_path.is_file() && lib_entrypoint.is_file() { - let crate_name = - crate::project_package_name(&cargo_manifest_path).ok_or_else(|| { - IntralinkError::LoadStdLibError(format!( - "failed to load manifest in \"{}\"", - cargo_manifest_path.display() - )) - })?; - let crate_info = Crate { name: crate_name, entrypoint: lib_entrypoint }; - - std_libs.push(crate_info); - } - } - - Ok(std_libs) -} - #[allow(clippy::too_many_lines)] #[cfg(test)] mod tests { + /* WIP! Adapt tests use super::*; use indoc::indoc; - use module_walker::walk_module_items; - use std::cell::RefCell; - - fn item_path(id: &str) -> ItemPath { - ItemPath::from_string(id).unwrap() - } - - #[test] - fn test_item_path_is_toplevel() { - assert!(!item_path("crate::baz::mumble").is_toplevel()); - assert!(!item_path("::std::baz::mumble").is_toplevel()); - assert!(!item_path("crate::baz").is_toplevel()); - assert!(!item_path("::std::baz").is_toplevel()); - assert!(item_path("crate").is_toplevel()); - assert!(item_path("::std").is_toplevel()); - } - - #[test] - fn test_item_path_parent() { - assert_eq!(item_path("crate::baz::mumble").parent(), Some(item_path("crate::baz"))); - assert_eq!(item_path("::std::baz::mumble").parent(), Some(item_path("::std::baz"))); - assert_eq!(item_path("crate::baz").parent(), Some(item_path("crate"))); - assert_eq!(item_path("::std::baz").parent(), Some(item_path("::std"))); - assert_eq!(item_path("crate").parent(), None); - assert_eq!(item_path("::std").parent(), None); - } - - #[test] - fn test_item_path_join() { - assert_eq!(item_path("crate::foo").join(&"bar"), item_path("crate::foo::bar"),); - assert_eq!(item_path("::std::foo").join(&"bar"), item_path("::std::foo::bar"),); - - assert_eq!( - item_path("::std::foo::bar").parent().unwrap().join(&"baz"), - item_path("::std::foo::baz"), - ); - } - - #[test] - fn test_all_ancestor_paths() { - let symbols = [ - item_path("crate::foo::bar::baz"), - item_path("crate::baz::mumble"), - item_path("::std::vec::Vec"), - ]; - let expected: HashSet = [ - item_path("crate"), - item_path("crate::foo"), - item_path("crate::foo::bar"), - item_path("crate::baz"), - item_path("::std"), - item_path("::std::vec"), - ] - .into_iter() - .collect(); - - assert_eq!(all_ancestor_paths(symbols.iter()), expected); - } - - fn explore_crate( - ast: &[Item], - dir: &Path, - crate_symbol: &ItemPath, - should_explore_module: impl Fn(&ItemPath) -> bool, - symbols_type: &mut HashMap, - emit_warning: impl Fn(&str), - ) { - let mut modules_visited: HashSet = HashSet::new(); - - symbols_type.insert(crate_symbol.clone(), SymbolType::Crate); - - let mut visit = |module: &ItemPath, item: &Item| { - visit_module_item(|_| true, symbols_type, module, item); - }; - - let mut explore_module = |mod_symbol: &ItemPath, mod_item: &ItemMod| -> bool { - check_explore_module(&should_explore_module, &mut modules_visited, mod_symbol, mod_item) - }; - - walk_module_items(ast, dir, crate_symbol, &mut visit, &mut explore_module, &emit_warning) - .ok() - .unwrap(); - } - - #[test] - fn test_walk_module_and_symbols_type() { - let module_skip: ItemPath = item_path("crate::skip"); - - let source = indoc! { " - struct AStruct {} - - mod skip { - struct Skip {} - } - - mod a { - mod b { - trait ATrait {} - } - - struct FooStruct {} - } - " - }; - - let mut symbols_type: HashMap = HashMap::new(); - let warnings = RefCell::new(Vec::new()); - - explore_crate( - &syn::parse_file(source).unwrap().items, - &PathBuf::new(), - &item_path("crate"), - |m| *m != module_skip, - &mut symbols_type, - |msg| warnings.borrow_mut().push(msg.to_owned()), - ); - - let expected: HashMap = [ - (item_path("crate"), SymbolType::Crate), - (item_path("crate::AStruct"), SymbolType::Struct), - (item_path("crate::skip"), SymbolType::Mod), - (item_path("crate::a"), SymbolType::Mod), - (item_path("crate::a::b"), SymbolType::Mod), - (item_path("crate::a::b::ATrait"), SymbolType::Trait), - (item_path("crate::a::FooStruct"), SymbolType::Struct), - ] - .into_iter() - .collect(); - - assert_eq!(symbols_type, expected); - } - #[test] - fn test_symbols_type_with_mod_under_cfg_test() { - let source = indoc! { " - #[cfg(not(test))] - mod a { - struct MyStruct {} - } - - #[cfg(test)] - mod a { - struct MyStructTest {} - } - - #[cfg(test)] - mod b { - struct MyStructTest {} - } - - #[cfg(not(test))] - mod b { - struct MyStruct {} - } - " - }; - - let mut symbols_type: HashMap = HashMap::new(); - let warnings = RefCell::new(Vec::new()); - - explore_crate( - &syn::parse_file(source).unwrap().items, - &PathBuf::new(), - &item_path("crate"), - |_| true, - &mut symbols_type, - |msg| warnings.borrow_mut().push(msg.to_owned()), - ); - - let expected: HashMap = [ - (item_path("crate"), SymbolType::Crate), - (item_path("crate::a"), SymbolType::Mod), - (item_path("crate::a::MyStruct"), SymbolType::Struct), - (item_path("crate::b"), SymbolType::Mod), - (item_path("crate::b::MyStruct"), SymbolType::Struct), - ] - .into_iter() - .collect(); - - assert_eq!(symbols_type, expected); - } - - #[test] - fn test_symbols_type_multiple_module_first_wins() { - let source = indoc! { " - #[cfg(not(foo))] - mod a { - struct MyStruct {} - } - - #[cfg(foo)] - mod a { - struct Skip {} - } - " - }; - - let mut symbols_type: HashMap = HashMap::new(); - let warnings = RefCell::new(Vec::new()); - - explore_crate( - &syn::parse_file(source).unwrap().items, - &PathBuf::new(), - &item_path("crate"), - |_| true, - &mut symbols_type, - |msg| warnings.borrow_mut().push(msg.to_owned()), - ); - - let expected: HashMap = [ - (item_path("crate"), SymbolType::Crate), - (item_path("crate::a"), SymbolType::Mod), - (item_path("crate::a::MyStruct"), SymbolType::Struct), - ] - .into_iter() - .collect(); - - assert_eq!(symbols_type, expected); - } - - #[test] - fn test_traverse_module_expore_lazily() { - let symbols: HashSet = [item_path("crate::module")].into_iter().collect(); - let modules = all_ancestor_paths(symbols.iter()); - - let source = indoc! { " - mod module { - struct Foo {} - } - " - }; - - let mut symbols_type: HashMap = HashMap::new(); - let warnings = RefCell::new(Vec::new()); - - explore_crate( - &syn::parse_file(source).unwrap().items, - &PathBuf::new(), - &item_path("crate"), - |module| modules.contains(module), - &mut symbols_type, - |msg| warnings.borrow_mut().push(msg.to_owned()), - ); - - let symbols_type: HashSet = symbols_type.keys().cloned().collect(); - - // We should still get `crate::module`, but nothing inside it. - let expected: HashSet = - [item_path("crate"), item_path("crate::module")].into_iter().collect(); - - assert_eq!(symbols_type, expected); - } + // WIP! check removed tests: maybe they can exist in some other form #[test] fn test_documentation_url() { @@ -1272,39 +524,6 @@ mod tests { ); } - #[test] - fn test_extract_markdown_intralink_symbols() { - let doc = indoc! { " - # Foobini - - This [beautiful crate](crate) is cool because it contains [modules](crate::amodule) - and some other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. - - Go ahead and check all the [structs in foo](crate::foo#structs). - Also check [this](::std::sync::Arc) and [this](::alloc::sync::Arc). - - We also support [reference][style] [links]. - - [style]: crate::amodule - [links]: crate::foo#structs - " - }; - - let symbols = extract_markdown_intralink_symbols(&Doc::from_str(doc)); - - let expected: HashSet = [ - item_path("crate"), - item_path("crate::amodule"), - item_path("crate::foo"), - item_path("::std::sync::Arc"), - item_path("::alloc::sync::Arc"), - ] - .into_iter() - .collect(); - - assert_eq!(symbols, expected); - } - #[test] fn test_rewrite_markdown_links() { let doc = indoc! { r" @@ -1335,9 +554,7 @@ mod tests { let new_readme = rewrite_markdown_links( &Doc::from_str(doc), - &symbols_type, - "foobini", - &|_| (), + &HashMap::new(), &IntralinksConfig::default(), &HashSet::new(), ); @@ -1396,9 +613,7 @@ mod tests { let new_readme = rewrite_links( &Doc::from_str(doc), - &symbols_type, - "foobini", - &|_| (), + &HashMap::new(), &IntralinksConfig { strip_links: Some(true), ..Default::default() }, ); let expected = indoc! { r" @@ -1456,9 +671,7 @@ mod tests { let new_readme = rewrite_markdown_links( &Doc::from_str(doc), - &symbols_type, - "foobini", - &|_| (), + &HashMap::new(), &IntralinksConfig::default(), &HashSet::new(), ); @@ -1524,9 +737,7 @@ mod tests { let new_readme = rewrite_links( &Doc::from_str(doc), - &symbols_type, - "foobini", - &|_| (), + &HashMap::new(), &IntralinksConfig::default(), ); let expected = indoc! { r#" @@ -1576,9 +787,7 @@ mod tests { let new_readme = rewrite_links( &Doc::from_str(doc), - &symbols_type, - "foobini", - &|_| (), + &HashMap::new(), &IntralinksConfig::default(), ); let expected = indoc! { r" @@ -1637,9 +846,7 @@ mod tests { let new_readme = rewrite_links( &Doc::from_str(doc), - &symbols_type, - "foobini", - &|_| (), + &HashMap::new(), &IntralinksConfig::default(), ); let expected = indoc! { r#" @@ -1663,4 +870,6 @@ mod tests { assert_eq!(new_readme.as_string(), expected); } + + */ } diff --git a/src/transform/intralinks/module_walker.rs b/src/transform/intralinks/module_walker.rs deleted file mode 100644 index 5f1ce66..0000000 --- a/src/transform/intralinks/module_walker.rs +++ /dev/null @@ -1,118 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -use std::path::{Path, PathBuf}; -use syn::Ident; -use syn::Item; -use thiserror::Error; - -use super::ItemPath; - -#[derive(Error, Debug)] -pub enum ModuleWalkError { - #[error("IO error: {0}")] - IOError(std::io::Error), - #[error("failed to parse rust file: {0}")] - ParseError(syn::Error), -} - -impl From for ModuleWalkError { - fn from(err: std::io::Error) -> Self { - ModuleWalkError::IOError(err) - } -} - -impl From for ModuleWalkError { - fn from(err: syn::Error) -> Self { - ModuleWalkError::ParseError(err) - } -} - -fn file_ast>(filepath: P) -> Result { - let src = std::fs::read_to_string(filepath)?; - - Ok(syn::parse_file(&src)?) -} - -/// Determines the module filename, which can be `.rs` or `/mod.rs`. -fn module_filename(dir: &Path, module: &Ident) -> Option { - let mod_file = dir.join(format!("{module}.rs")); - - if mod_file.is_file() { - return Some(mod_file); - } - - let mod_file = dir.join(module.to_string()).join("mod.rs"); - - if mod_file.is_file() { - return Some(mod_file); - } - - None -} - -pub(super) fn walk_module_items( - ast: &[Item], - dir: &Path, - mod_symbol: &ItemPath, - visit: &mut impl FnMut(&ItemPath, &Item), - explore_module: &mut impl FnMut(&ItemPath, &syn::ItemMod) -> bool, - emit_warning: &impl Fn(&str), -) -> Result<(), ModuleWalkError> { - for item in ast { - visit(mod_symbol, item); - - if let Item::Mod(module) = item { - let child_module_symbol: ItemPath = mod_symbol.clone().join(&module.ident.to_string()); - - if explore_module(&child_module_symbol, module) { - match &module.content { - Some((_, items)) => { - walk_module_items( - items, - dir, - &child_module_symbol, - visit, - explore_module, - emit_warning, - )?; - } - None => match module_filename(dir, &module.ident) { - None => emit_warning(&format!( - "Unable to find module file for module {} in directory \"{}\"", - child_module_symbol, - dir.display() - )), - Some(mod_filename) => walk_module_file( - mod_filename, - &child_module_symbol, - visit, - explore_module, - emit_warning, - )?, - }, - } - } - } - } - - Ok(()) -} - -pub(super) fn walk_module_file>( - file: P, - mod_symbol: &ItemPath, - visit: &mut impl FnMut(&ItemPath, &Item), - explore_module: &mut impl FnMut(&ItemPath, &syn::ItemMod) -> bool, - emit_warning: &impl Fn(&str), -) -> Result<(), ModuleWalkError> { - let dir: &Path = file - .as_ref() - .parent() - .unwrap_or_else(|| panic!("failed to get directory of \"{}\"", file.as_ref().display())); - let ast: syn::File = file_ast(&file)?; - - walk_module_items(&ast.items, dir, mod_symbol, visit, explore_module, emit_warning) -} diff --git a/src/transform/intralinks/rustdoc.rs b/src/transform/intralinks/rustdoc.rs new file mode 100644 index 0000000..609ccea --- /dev/null +++ b/src/transform/intralinks/rustdoc.rs @@ -0,0 +1,561 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +use crate::transform::intralinks::links::Link; +use crate::transform::intralinks::ItemPath; +use crate::transform::{IntralinkError, IntralinksConfig, IntralinksDocsRsConfig}; +use crate::PackageTarget; +use itertools::Itertools; +use rustdoc_json::BuildError; +use rustdoc_types::{ + Crate, ExternalCrate, Id as ItemId, Impl, Item, ItemEnum, ItemSummary, MacroKind, Primitive, + Struct, StructKind, Trait, Type, +}; +use rustdoc_types::{Enum, ProcMacro, Union}; +use std::collections::HashMap; +use std::path::{Path, PathBuf}; + +const EXPECTED_RUSTDOC_FORMAT_VERSION: u32 = 30; + +fn crate_from_file(path: &Path) -> Result { + let json = std::fs::read_to_string(path) + .map_err(|io_error| IntralinkError::ReadRustdocError { io_error })?; + serde_json::from_str(&json).map_err(|_| IntralinkError::ParseRustdocError) +} + +fn crate_rustdoc_intralinks(c: &Crate) -> &HashMap { + &c.index.get(&c.root).expect("root id not present in index").links +} + +#[derive(Debug, Clone)] +struct ItemInfo<'a> { + crate_id: u32, + path: ItemPath<'a>, + kind: ItemKind, + parent_kind: Option, +} + +impl<'a> ItemInfo<'a> { + fn new( + crate_id: u32, + path: ItemPath<'a>, + kind: ItemKind, + parent_kind: Option, + ) -> ItemInfo<'a> { + ItemInfo { crate_id, path, kind, parent_kind } + } + + fn from( + item_summary: &'a ItemSummary, + parent_kind: Option, + item_context: ItemContext, + ) -> ItemInfo<'a> { + ItemInfo::new( + item_summary.crate_id, + ItemPath::new(&item_summary.path), + ItemKind::from_rustdoc_item_kind(&item_summary.kind, item_context), + parent_kind, + ) + } + + /// Merges all the information of both items. + fn merge(&self, other: &ItemInfo<'a>) -> Option> { + if self.crate_id != other.crate_id { + return None; + } + if self.path != other.path { + return None; + } + if self.kind != other.kind { + return None; + } + + if self.parent_kind.zip(other.parent_kind).is_some_and(|(s, o)| s != o) { + return None; + } + + let merged = ItemInfo { + crate_id: self.crate_id, + path: self.path.clone(), + kind: self.kind, + parent_kind: self.parent_kind.or(other.parent_kind), + }; + + Some(merged) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum ItemKind { + Module, + ExternCrate, + Import, + Struct, + StructField, + Union, + Enum, + Variant, + Function, + TypeAlias, + OpaqueTy, + Constant, + Trait, + TraitAlias, + Impl, + Static, + ForeignType, + Macro, + ProcAttribute, + ProcDerive, + AssocConst, + AssocType, + Primitive, + Keyword, + + // Kinds that do not exist in rustdoc_types::ItemKind: + Method, + TyMethod, +} + +impl ItemKind { + fn from_rustdoc_item_kind( + kind: &rustdoc_types::ItemKind, + item_context: ItemContext, + ) -> ItemKind { + match kind { + rustdoc_types::ItemKind::Module => ItemKind::Module, + rustdoc_types::ItemKind::ExternCrate => ItemKind::ExternCrate, + rustdoc_types::ItemKind::Import => ItemKind::Import, + rustdoc_types::ItemKind::Struct => ItemKind::Struct, + rustdoc_types::ItemKind::StructField => ItemKind::StructField, + rustdoc_types::ItemKind::Union => ItemKind::Union, + rustdoc_types::ItemKind::Enum => ItemKind::Enum, + rustdoc_types::ItemKind::Variant => ItemKind::Variant, + rustdoc_types::ItemKind::Function => match item_context { + ItemContext::Normal => ItemKind::Function, + ItemContext::Impl => ItemKind::Method, + ItemContext::Trait => ItemKind::TyMethod, + }, + rustdoc_types::ItemKind::TypeAlias => ItemKind::TypeAlias, + rustdoc_types::ItemKind::OpaqueTy => ItemKind::OpaqueTy, + rustdoc_types::ItemKind::Constant => ItemKind::Constant, + rustdoc_types::ItemKind::Trait => ItemKind::Trait, + rustdoc_types::ItemKind::TraitAlias => ItemKind::TraitAlias, + rustdoc_types::ItemKind::Impl => ItemKind::Impl, + rustdoc_types::ItemKind::Static => ItemKind::Static, + rustdoc_types::ItemKind::ForeignType => ItemKind::ForeignType, + rustdoc_types::ItemKind::Macro => ItemKind::Macro, + rustdoc_types::ItemKind::ProcAttribute => ItemKind::ProcAttribute, + rustdoc_types::ItemKind::ProcDerive => ItemKind::ProcDerive, + rustdoc_types::ItemKind::AssocConst => ItemKind::AssocConst, + rustdoc_types::ItemKind::AssocType => ItemKind::AssocType, + rustdoc_types::ItemKind::Primitive => ItemKind::Primitive, + rustdoc_types::ItemKind::Keyword => ItemKind::Keyword, + } + } + + fn of_item(item: &Item, item_context: ItemContext) -> ItemKind { + match item.inner { + ItemEnum::Module(_) => ItemKind::Module, + ItemEnum::ExternCrate { .. } => ItemKind::ExternCrate, + ItemEnum::Import(_) => ItemKind::Import, + ItemEnum::Union(_) => ItemKind::Union, + ItemEnum::Struct(_) => ItemKind::Struct, + ItemEnum::StructField(_) => ItemKind::StructField, + ItemEnum::Enum(_) => ItemKind::Enum, + ItemEnum::Variant(_) => ItemKind::Variant, + ItemEnum::Function(_) => match item_context { + ItemContext::Normal => ItemKind::Function, + ItemContext::Impl => ItemKind::Method, + ItemContext::Trait => ItemKind::TyMethod, + }, + ItemEnum::Trait(_) => ItemKind::Trait, + ItemEnum::TraitAlias(_) => ItemKind::TraitAlias, + ItemEnum::Impl(_) => ItemKind::Impl, + ItemEnum::TypeAlias(_) => ItemKind::TypeAlias, + ItemEnum::OpaqueTy(_) => ItemKind::OpaqueTy, + ItemEnum::Constant { .. } => ItemKind::Constant, + ItemEnum::Static(_) => ItemKind::Static, + ItemEnum::ForeignType => ItemKind::ForeignType, + ItemEnum::Macro(_) => ItemKind::Macro, + ItemEnum::ProcMacro(ProcMacro { kind: MacroKind::Bang, .. }) => ItemKind::Macro, + ItemEnum::ProcMacro(ProcMacro { kind: MacroKind::Derive, .. }) => ItemKind::ProcDerive, + ItemEnum::ProcMacro(ProcMacro { kind: MacroKind::Attr, .. }) => ItemKind::ProcAttribute, + ItemEnum::Primitive(_) => ItemKind::Primitive, + ItemEnum::AssocConst { .. } => ItemKind::AssocConst, + ItemEnum::AssocType { .. } => ItemKind::AssocType, + } + } +} + +fn child_item_ids<'a>(item: &'a Item) -> Box + 'a> { + match &item.inner { + ItemEnum::Struct(Struct { kind, impls, .. }) => { + let fields_ids: Box> = match kind { + StructKind::Unit => Box::new(std::iter::empty()), + StructKind::Tuple(ids) => Box::new(ids.iter().filter_map(|id| id.as_ref())), + StructKind::Plain { fields, .. } => Box::new(fields.iter()), + }; + + Box::new(fields_ids.chain(impls.iter())) + } + ItemEnum::Impl(Impl { trait_: Some(_), .. }) => Box::new(std::iter::empty()), + ItemEnum::Impl(Impl { items: item_ids, for_, .. }) => match for_ { + Type::ResolvedPath(_) => Box::new(item_ids.iter()), + _ => Box::new(std::iter::empty()), + }, + ItemEnum::Union(Union { fields, impls, .. }) => Box::new(fields.iter().chain(impls.iter())), + ItemEnum::Enum(Enum { variants, impls, .. }) => { + Box::new(variants.iter().chain(impls.iter())) + } + ItemEnum::Primitive(Primitive { impls, .. }) => Box::new(impls.iter()), + ItemEnum::Trait(Trait { items, .. }) => { + // We ignore the implementations of the trait as their items are not part of the trait + // itself. + Box::new(items.iter()) + } + + ItemEnum::Function(_) + | ItemEnum::ExternCrate { .. } + | ItemEnum::Import(_) + | ItemEnum::Module(_) + | ItemEnum::Constant { .. } + | ItemEnum::Static(_) + | ItemEnum::Macro(_) + | ItemEnum::ProcMacro(_) + | ItemEnum::AssocConst { .. } + | ItemEnum::AssocType { .. } + | ItemEnum::StructField(_) + | ItemEnum::Variant(_) + | ItemEnum::ForeignType + | ItemEnum::TraitAlias(_) + | ItemEnum::TypeAlias(_) + | ItemEnum::OpaqueTy(_) => Box::new(std::iter::empty()), + } +} + +#[derive(Clone, Copy, Debug)] +enum ItemContext { + Normal, + Impl, + Trait, +} + +fn get_item_info<'a>( + item_id: &ItemId, + parent_path: &ItemPath<'a>, + parent_kind: Option, + item_context: ItemContext, + rustdoc_crate: &'a Crate, +) -> Option> { + match rustdoc_crate.paths.get(item_id) { + Some(item_summary) => Some(ItemInfo::from(item_summary, parent_kind, item_context)), + None => rustdoc_crate.index.get(item_id).map(|item| { + let path = match item.name.as_ref() { + None => parent_path.clone(), + Some(name) => parent_path.add(name.clone()), + }; + let item_kind = ItemKind::of_item(item, item_context); + + ItemInfo::new(item.crate_id, path, item_kind, parent_kind) + }), + } +} + +// WIP! are we using the right abstraction here? +fn transitive_items<'a>( + item_id: &'a ItemId, + item_info: &ItemInfo<'a>, + item_context: ItemContext, + rustdoc_crate: &'a Crate, + items_info: &mut HashMap<&'a ItemId, ItemInfo<'a>>, +) { + if item_info.kind != ItemKind::Impl { + items_info + .entry(item_id) + .and_modify(|existing_item_info| { + *existing_item_info = + existing_item_info.merge(item_info).expect("unmergeable item info"); + }) + .or_insert_with(|| item_info.clone()); + } + + let Some(item) = rustdoc_crate.index.get(item_id) else { + // This item is not in the index for some reason... + return; + }; + + let inner_item_context = match item.inner { + ItemEnum::Trait(_) => ItemContext::Trait, + ItemEnum::Impl(_) => ItemContext::Impl, + _ => item_context, + }; + + for inner_item_id in child_item_ids(item) { + // The inner_item_parent_kind is not just `item_info.kind` because we need to skip + // kinds like `impl` blocks. + let inner_item_parent_kind = match item.name { + Some(_) => Some(item_info.kind), + None => item_info.parent_kind, + }; + + let inner_item_info = get_item_info( + inner_item_id, + &item_info.path, + inner_item_parent_kind, + inner_item_context, + rustdoc_crate, + ); + + if let Some(inner_item_info) = inner_item_info { + transitive_items( + inner_item_id, + &inner_item_info, + inner_item_context, + rustdoc_crate, + items_info, + ); + } + } +} + +pub struct IntralinkResolver<'a> { + link_url: HashMap, + config: &'a IntralinksDocsRsConfig, + package_name: &'a str, +} + +impl<'a> IntralinkResolver<'a> { + pub fn new(package_name: &'a str, config: &'a IntralinksDocsRsConfig) -> IntralinkResolver<'a> { + IntralinkResolver { link_url: HashMap::new(), package_name, config } + } + + fn url_segment(kind: ItemKind, name: &str) -> String { + match kind { + ItemKind::Module => format!("{name}/"), + ItemKind::Struct => format!("struct.{name}.html"), + ItemKind::StructField => format!("#structfield.{name}"), + ItemKind::Union => format!("union.{name}.html"), + ItemKind::Enum => format!("enum.{name}.html"), + ItemKind::Variant => format!("#variant.{name}"), + ItemKind::Function => format!("fn.{name}.html"), + ItemKind::Method => format!("#method.{name}"), + ItemKind::TyMethod => format!("#tymethod.{name}"), + ItemKind::TypeAlias => format!("type.{name}.html"), + ItemKind::OpaqueTy => format!("type.{name}.html"), + ItemKind::Constant => format!("const.{name}.html"), + ItemKind::Trait => format!("trait.{name}.html"), + ItemKind::TraitAlias => format!("traitalias.{name}.html"), + ItemKind::Static => format!("static.{name}.html"), + ItemKind::Macro => format!("macro.{name}.html"), + ItemKind::ProcAttribute => format!("attr.{name}.html"), + ItemKind::ProcDerive => format!("derive.{name}.html"), + ItemKind::AssocConst => { + format!("#associatedconstant.{name}") + } + ItemKind::AssocType => format!("#associatedtype.{name}"), + ItemKind::Primitive => format!("primitive.{name}.html"), + + ItemKind::Keyword + | ItemKind::ExternCrate + | ItemKind::Import + | ItemKind::Impl + | ItemKind::ForeignType => { + unreachable!("items of kind {:?} cannot be intralinked to", kind); + } + } + } + + fn is_stdlib_crate(external_crate: &ExternalCrate) -> bool { + external_crate + .html_root_url + .as_deref() + .is_some_and(|base_url| base_url.starts_with("https://doc.rust-lang.org/")) + } + + fn make_url(base_url: &str, package_name: &str, version: &str, url_path: &str) -> String { + format!("{base_url}/{package_name}/{version}/{url_path}") + } + + fn add( + &mut self, + link: Link, + item_info: &ItemInfo, + external_crates: &HashMap, + ) { + let docs_rs_base_url = self.config.docs_rs_base_url.as_deref().unwrap_or("https://docs.rs"); + + let path_segment_kind = |i: usize| match item_info.path.len() - i { + 1 => item_info.kind, + 2 => item_info.parent_kind.unwrap_or(ItemKind::Module), + _ => ItemKind::Module, + }; + let url_path = item_info + .path + .segments() + .enumerate() + .map(|(i, segment)| (segment, path_segment_kind(i))) + .map(|(segment, item_kind)| IntralinkResolver::url_segment(item_kind, segment)) + .join(""); + + let url = match item_info.crate_id { + // Local crate has id 0. + 0 => { + let version = self.config.docs_rs_version.as_deref().unwrap_or("latest"); + let package_name = &self.package_name; + + Self::make_url(docs_rs_base_url, package_name, version, &url_path) + } + // External crate + _ => { + let Some(external_crate) = external_crates.get(&item_info.crate_id) else { + return; + }; + + match external_crate.html_root_url.as_deref() { + Some(base_url) => { + let base_url = match Self::is_stdlib_crate(external_crate) { + true => { + // TODO Once we are able to use the stable version we can remove this + // (https://github.com/rust-lang/rust/issues/76578). + base_url + .strip_suffix("/nightly/") + .map_or_else(|| base_url.to_owned(), |p| format!("{p}/stable/")) + } + false => base_url.to_owned(), + }; + + format!("{base_url}{url_path}") + } + None => { + let crate_name = &external_crate.name; + + // TODO We are using the crate name instead of the package name: that means that + // we might generate a wrong url. In most cases the crate name matches the + // package name. When it doesn't it is often because underscores in the + // crate name becomes dashes in the package name. Fortunately `docs.rs` + // will redirect in that case (e.g. https://docs.rs/tower_service/ will + // redirect to https://docs.rs/tower-service/latest/tower_service/). + // TODO We shouldn't hardcode "latest" here: we should get that information from + // the version rustdoc determined the crate was using. + Self::make_url(docs_rs_base_url, crate_name, "latest", &url_path) + } + } + } + }; + + self.link_url.insert(link, url); + } + + pub fn resolve_link(&self, link: &Link) -> Option<&str> { + self.link_url.get(link).map(String::as_str) + } + + pub fn is_intralink(link: &Link) -> bool { + let has_lone_colon = || link.raw_link.replace("::", "").contains(':'); + + !link.symbol().is_empty() && !link.raw_link.contains('/') && !has_lone_colon() + } +} + +fn run_rustdoc( + package_target: &PackageTarget, + workspace_package: Option<&str>, + manifest_path: &PathBuf, +) -> Result { + // WIP! you can check if rustup is installed with rustup_installed. + + let rustdoc_json_path: PathBuf = { + let target: rustdoc_json::PackageTarget = match package_target { + PackageTarget::Bin { crate_name } => { + rustdoc_json::PackageTarget::Bin(crate_name.clone()) + } + PackageTarget::Lib => rustdoc_json::PackageTarget::Lib, + }; + let mut stderr = Vec::new(); + + let mut builder = rustdoc_json::Builder::default() + // TODO Use stable when this stabilizes (https://github.com/rust-lang/rust/issues/76578). + .toolchain("nightly") + .manifest_path(manifest_path) + .document_private_items(true) + // WIP!simple We need to parameterize the features based on the configuration + // also "no default features" + .all_features(true) + .quiet(true) + .color(rustdoc_json::Color::Never) + .package_target(target); + + if let Some(package) = workspace_package { + builder = builder.package(package); + } + + let result = builder.build_with_captured_output(std::io::sink(), &mut stderr); + + result.map_err(|error| match error { + BuildError::BuildRustdocJsonError => match stderr.is_empty() { + true => IntralinkError::BuildRustdocError { + stderr: "Weirdly, rustdoc did not write anything to stderr".to_owned(), + }, + false => IntralinkError::BuildRustdocError { + stderr: String::from_utf8_lossy(&stderr).into_owned(), + }, + }, + e => IntralinkError::RustdocError { error: e }, + })? + }; + + // WIP! temp debug + eprintln!("json file: {}", rustdoc_json_path.display()); + + let rustdoc_crate = crate_from_file(&rustdoc_json_path)?; + + match rustdoc_crate.format_version { + EXPECTED_RUSTDOC_FORMAT_VERSION => Ok(rustdoc_crate), + format_version => Err(IntralinkError::UnsupportedRustdocFormatVersion { + version: format_version, + expected_version: EXPECTED_RUSTDOC_FORMAT_VERSION, + }), + } +} + +pub fn create_intralink_resolver<'a>( + package_name: &'a str, + package_target: &PackageTarget, + workspace_package: Option<&str>, + manifest_path: &PathBuf, + config: &'a IntralinksConfig, +) -> Result, IntralinkError> { + let rustdoc_crate = run_rustdoc(package_target, workspace_package, manifest_path)?; + + let mut items_info: HashMap<&ItemId, ItemInfo<'_>> = + HashMap::with_capacity(rustdoc_crate.index.len()); + + // WIP!simple this should be in a function of it's own. + for (item_id, item_summary) in &rustdoc_crate.paths { + let item_info = ItemInfo::from(item_summary, None, ItemContext::Normal); + + transitive_items(item_id, &item_info, ItemContext::Normal, &rustdoc_crate, &mut items_info); + } + + let links_items_id = crate_rustdoc_intralinks(&rustdoc_crate); + let mut intralink_resolver = IntralinkResolver::new(package_name, &config.docs_rs); + + for (link, item_id) in links_items_id { + let link = Link::new(link.clone()); + let Some(item_info) = items_info.get(item_id) else { + // We will fail when we try to create the link and will emit a warning there. + continue; + }; + + intralink_resolver.add(link, item_info, &rustdoc_crate.external_crates); + } + + Ok(intralink_resolver) +} + +// WIP! move the tests that belong here + +// WIP! support links like [`crate`] diff --git a/tests/option_cmd_intralinks_strip_links/README-expected.md b/tests/option_cmd_intralinks_strip_links/README-expected.md index 8661cfb..8fcf55e 100644 --- a/tests/option_cmd_intralinks_strip_links/README-expected.md +++ b/tests/option_cmd_intralinks_strip_links/README-expected.md @@ -5,7 +5,7 @@ This will not be [modified](crate::amodule). This beautiful crate is cool because it contains modules and some other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. -This link is broken, but this should [wor\\k \[ju\]st](f\\i\(n\)e). +This link is broken, but this should wor\\k \[ju\]st. Go ahead and check all the structs in foo specifically this one. Also, this is a nice function: copy. diff --git a/tests/option_cmd_workspace_dependency_collision/dependent/Cargo.toml b/tests/option_cmd_workspace_dependency_collision/dependent/Cargo.toml index 6032113..b41ecf1 100644 --- a/tests/option_cmd_workspace_dependency_collision/dependent/Cargo.toml +++ b/tests/option_cmd_workspace_dependency_collision/dependent/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" [dependencies] -# Depends on a published version of a crate with a lower version than the one -# of the same name in this workspace +# Depends on a published version of a crate with the same name in this workspace. We want to test that we pick the +# package in the workspace to extract the docs from. cargo-rdme = "1.4.3" diff --git a/tests/option_conf_file_intralinks_docs_rs_base_url/src/lib.rs b/tests/option_conf_file_intralinks_docs_rs_base_url/src/lib.rs index c8ee5c7..a9464cf 100644 --- a/tests/option_conf_file_intralinks_docs_rs_base_url/src/lib.rs +++ b/tests/option_conf_file_intralinks_docs_rs_base_url/src/lib.rs @@ -1,6 +1,6 @@ //! This [beautiful crate](crate) is cool because it contains [modules](crate::amodule) and may use //! [copy](::std::fs::copy). -mod amodule {} +pub mod amodule {} fn main() {} diff --git a/tests/option_conf_file_intralinks_strip_links/README-expected.md b/tests/option_conf_file_intralinks_strip_links/README-expected.md index 8661cfb..8fcf55e 100644 --- a/tests/option_conf_file_intralinks_strip_links/README-expected.md +++ b/tests/option_conf_file_intralinks_strip_links/README-expected.md @@ -5,7 +5,7 @@ This will not be [modified](crate::amodule). This beautiful crate is cool because it contains modules and some other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. -This link is broken, but this should [wor\\k \[ju\]st](f\\i\(n\)e). +This link is broken, but this should wor\\k \[ju\]st. Go ahead and check all the structs in foo specifically this one. Also, this is a nice function: copy. diff --git a/tests/testing.rs b/tests/testing.rs index 4867f7e..d73dc61 100644 --- a/tests/testing.rs +++ b/tests/testing.rs @@ -104,8 +104,8 @@ fn print_failure_readme_mismatch( write!( stream, "diff {} {}", + expected_readme_path.as_ref().display(), readme_path.as_ref().display(), - expected_readme_path.as_ref().display() ) .unwrap(); stream.reset().unwrap(); diff --git a/tests/tests.rs b/tests/tests.rs index a78d62d..4c62bf3 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -326,7 +326,7 @@ fn integration_test_transform_intralinks_simple() { } #[test] -fn integration_test_transform_intralinks_method() { +fn integration_test_transform_intralinks_impl_items() { run_test("transform_intralinks_impl_items"); } @@ -336,8 +336,8 @@ fn integration_test_transform_intralinks_reference_links() { } #[test] -fn integration_test_transform_intralinks_module_walk() { - run_test("transform_intralinks_module_walk"); +fn integration_test_transform_intralinks_multiple_modules() { + run_test("transform_intralinks_multiple_modules"); } #[test] @@ -436,3 +436,47 @@ fn integration_test_option_cmd_heading_base_level() { fn integration_test_crate_procmacro() { run_test("crate_procmacro"); } + +#[test] +fn integration_test_transform_intralinks_all_item_kinds() { + run_test("transform_intralinks_all_item_kinds"); +} + +#[test] +fn integration_test_transform_intralinks_procedural_macros() { + run_test("transform_intralinks_procedural_macros"); +} + +#[test] +fn integration_test_transform_intralinks_skip_rustdoc_when_no_intralinks() { + let test_name = "transform_intralinks_skip_rustdoc_when_no_intralinks"; + + // This checks that if rustdoc runs we actually fail, so that the real test before is + // actually asserting that rustdoc is not running because otherwise it would have failed. + let options = TestOptions { + args: &["--entrypoint", "bin:test-precondition"], + expected_exit_code: 1, + check_readme_expected: false, + ..TestOptions::default() + }; + run_test_with_options(test_name, &options); + + let options = + TestOptions { args: &["--entrypoint", "bin:real-test"], ..TestOptions::default() }; + run_test_with_options(test_name, &options); +} + +#[test] +fn integration_test_transform_intralinks_item_path_collision() { + run_test("transform_intralinks_item_path_collision"); +} + +#[test] +fn integration_test_transform_intralinks_package_and_crate_names() { + run_test("transform_intralinks_package_and_crate_names"); +} + +#[test] +fn integration_test_transform_intralinks_external_crate() { + run_test("transform_intralinks_external_crate"); +} diff --git a/tests/transform_intralinks_module_walk/Cargo.toml b/tests/transform_intralinks_all_item_kinds/Cargo.toml similarity index 100% rename from tests/transform_intralinks_module_walk/Cargo.toml rename to tests/transform_intralinks_all_item_kinds/Cargo.toml diff --git a/tests/transform_intralinks_all_item_kinds/README-expected.md b/tests/transform_intralinks_all_item_kinds/README-expected.md new file mode 100644 index 0000000..9496790 --- /dev/null +++ b/tests/transform_intralinks_all_item_kinds/README-expected.md @@ -0,0 +1,36 @@ + + +All item kinds: + +* [std](https://doc.rust-lang.org/stable/std/) +* [Arc](https://doc.rust-lang.org/stable/alloc/sync/struct.Arc.html) +* [core::cmp::Eq](https://doc.rust-lang.org/stable/core/cmp/trait.Eq.html) +* [Path](https://doc.rust-lang.org/stable/std/path/struct.Path.html) +* [u32](https://doc.rust-lang.org/stable/std/primitive.u32.html) +* [crate](https://docs.rs/integration_test/latest/integration_test/) +* [amodule](https://docs.rs/integration_test/latest/integration_test/amodule/) +* [foo::BestStruct](https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html) +* [MyUnion](https://docs.rs/integration_test/latest/integration_test/union.MyUnion.html) +* [MyUnion::f1](https://docs.rs/integration_test/latest/integration_test/union.MyUnion.html#structfield.f1) +* [MyEnum](https://docs.rs/integration_test/latest/integration_test/enum.MyEnum.html) +* [my_fn](https://docs.rs/integration_test/latest/integration_test/fn.my_fn.html) +* [my_fn()](https://docs.rs/integration_test/latest/integration_test/fn.my_fn.html) +* [foobini::MyType](https://docs.rs/integration_test/latest/integration_test/foobini/type.MyType.html) +* [foobini::MyOpaqueType](https://docs.rs/integration_test/latest/integration_test/foobini/type.MyOpaqueType.html) +* [MY_CONST](https://docs.rs/integration_test/latest/integration_test/const.MY_CONST.html) +* [MyTrait](https://docs.rs/integration_test/latest/integration_test/trait.MyTrait.html) +* [MyTrait::T](https://docs.rs/integration_test/latest/integration_test/trait.MyTrait.html#associatedtype.T) +* [MyTrait::trait_method](https://docs.rs/integration_test/latest/integration_test/trait.MyTrait.html#tymethod.trait_method) +* [MyTraitAlias](https://docs.rs/integration_test/latest/integration_test/traitalias.MyTraitAlias.html) +* [MY_STATIC](https://docs.rs/integration_test/latest/integration_test/static.MY_STATIC.html) +* [my_macro](https://docs.rs/integration_test/latest/integration_test/macro.my_macro.html) +* [bar::BestStruct](https://docs.rs/integration_test/latest/integration_test/bar/struct.BestStruct.html) +* [bar::BestStruct::my_field](https://docs.rs/integration_test/latest/integration_test/bar/struct.BestStruct.html#structfield.my_field) +* [bar::BestStruct::0](https://docs.rs/integration_test/latest/integration_test/bar/struct.TupleStruct.html#structfield.0) +* [MyEnum::Foo](https://docs.rs/integration_test/latest/integration_test/enum.MyEnum.html#variant.Foo) +* [StructWithImpl::NUMBER](https://docs.rs/integration_test/latest/integration_test/struct.StructWithImpl.html#associatedconstant.NUMBER) +* [StructWithImpl::Baz](https://docs.rs/integration_test/latest/integration_test/struct.StructWithImpl.html#associatedtype.Baz) +* [StructWithImpl::a_method](https://docs.rs/integration_test/latest/integration_test/struct.StructWithImpl.html#method.a_method) +* [Foobini](https://docs.rs/integration_test/latest/integration_test/re/struct.Foobini.html) + + diff --git a/tests/transform_intralinks_module_walk/README-template.md b/tests/transform_intralinks_all_item_kinds/README-template.md similarity index 100% rename from tests/transform_intralinks_module_walk/README-template.md rename to tests/transform_intralinks_all_item_kinds/README-template.md diff --git a/tests/transform_intralinks_all_item_kinds/src/lib.rs b/tests/transform_intralinks_all_item_kinds/src/lib.rs new file mode 100644 index 0000000..8a1f6bf --- /dev/null +++ b/tests/transform_intralinks_all_item_kinds/src/lib.rs @@ -0,0 +1,110 @@ +//! All item kinds: +//! +//! * [std](std) +//! * [Arc](std::sync::Arc) +//! * [core::cmp::Eq](core::cmp::Eq) +//! * [Path](Path) +//! * [u32](u32) +//! * [crate](crate) +//! * [amodule](amodule) +//! * [foo::BestStruct](foo::BestStruct) +//! * [MyUnion](MyUnion) +//! * [MyUnion::f1](MyUnion::f1) +//! * [MyEnum](MyEnum) +//! * [my_fn](my_fn) +//! * [my_fn()](my_fn()) +//! * [foobini::MyType](foobini::MyType) +//! * [foobini::MyOpaqueType](foobini::MyOpaqueType) +//! * [MY_CONST](MY_CONST) +//! * [MyTrait](MyTrait) +//! * [MyTrait::T](MyTrait::T) +//! * [MyTrait::trait_method](MyTrait::trait_method) +//! * [MyTraitAlias](MyTraitAlias) +//! * [MY_STATIC](MY_STATIC) +//! * [my_macro](my_macro) +//! * [bar::BestStruct](bar::BestStruct) +//! * [bar::BestStruct::my_field](bar::BestStruct::my_field) +//! * [bar::BestStruct::0](bar::TupleStruct::0) +//! * [MyEnum::Foo](MyEnum::Foo) +//! * [StructWithImpl::NUMBER](StructWithImpl::NUMBER) +//! * [StructWithImpl::Baz](StructWithImpl::Baz) +//! * [StructWithImpl::a_method](StructWithImpl::a_method) +//! * [Foobini](Foobini) + +// TODO Remove this when `type_alias_impl_trait` is stable (https://github.com/rust-lang/rust/issues/63063). +#![feature(type_alias_impl_trait)] +// TODO Remove this when `trait_alias` is stable (https://github.com/rust-lang/rust/issues/41517). +#![feature(trait_alias)] +// TODO Remove this when inherent_associated_types stabilizes (https://github.com/rust-lang/rust/issues/8995). +// This is what allows the `type` definition in `impl StructWithImpl`. +#![feature(inherent_associated_types)] + +extern crate core; + +pub use std::path::Path; + +#[macro_export] +macro_rules! my_macro { + () => {{}}; +} + +pub const MY_CONST: usize = 32; + +pub static MY_STATIC: usize = 32; + +pub mod amodule {} + +pub mod foo { + pub struct BestStruct { + pub my_field: u64, + } +} + +pub mod bar { + pub struct BestStruct { + pub my_field: u64, + } + + pub struct TupleStruct(pub u64); +} + +pub union MyUnion { + f1: u32, +} + +pub enum MyEnum { + Foo, +} + +pub fn my_fn() {} + +pub mod foobini { + pub type MyType = usize; + pub type MyOpaqueType = impl Iterator; +} + +pub trait MyTrait { + type T; + + fn trait_method(); +} + +pub trait MyTraitAlias = std::fmt::Debug + Send; + +pub struct StructWithImpl {} + +impl StructWithImpl { + const NUMBER: usize = 1234; + + type Baz = u32; + + pub fn a_method(&self) -> u32 { + 42 + } +} + +pub use re::Foobini; + +mod re { + pub struct Foobini {} +} \ No newline at end of file diff --git a/tests/transform_intralinks_ambiguous_module/src/main.rs b/tests/transform_intralinks_ambiguous_module/src/main.rs index e81e629..3145a88 100644 --- a/tests/transform_intralinks_ambiguous_module/src/main.rs +++ b/tests/transform_intralinks_ambiguous_module/src/main.rs @@ -7,43 +7,43 @@ //! [d Same is a trait](crate::d::Same) #[cfg(not(foo))] -mod a { - struct MyStruct {} +pub mod a { + pub struct MyStruct {} } #[cfg(foo)] -mod a { - struct Skip {} +pub mod a { + pub struct Skip {} } #[cfg(not(test))] -mod b { - struct MyStruct {} +pub mod b { + pub struct MyStruct {} } #[cfg(test)] -mod b { - struct MyStructTest {} +pub mod b { + pub struct MyStructTest {} } #[cfg(test)] -mod c { - struct MyStructTest {} +pub mod c { + pub struct MyStructTest {} } #[cfg(not(test))] -mod c { - struct MyStruct {} +pub mod c { + pub struct MyStruct {} } #[cfg(test)] -mod d { - struct Same {} +pub mod d { + pub struct Same {} } #[cfg(not(test))] -mod d { - trait Same {} +pub mod d { + pub trait Same {} } fn main() {} diff --git a/tests/transform_intralinks_backticked/README-expected.md b/tests/transform_intralinks_backticked/README-expected.md index 8a9f87b..f9426c4 100644 --- a/tests/transform_intralinks_backticked/README-expected.md +++ b/tests/transform_intralinks_backticked/README-expected.md @@ -5,7 +5,7 @@ This will not be [modified](crate::amodule). This [beautiful crate](https://docs.rs/integration_test/latest/integration_test/) is cool because it contains [modules](https://docs.rs/integration_test/latest/integration_test/amodule/) and some other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. -This link is broken, but this should [wor\\k \[ju\]st](f\\i\(n\)e). +This link is broken, but this should wor\\k \[ju\]st. Go ahead and check all the [structs in foo](https://docs.rs/integration_test/latest/integration_test/foo/#structs) and [structs in foo](https://docs.rs/integration_test/latest/integration_test/foo/#structs) specifically [this one](https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html) diff --git a/tests/transform_intralinks_backticked/src/main.rs b/tests/transform_intralinks_backticked/src/main.rs index 45ff9dc..0a5a57d 100644 --- a/tests/transform_intralinks_backticked/src/main.rs +++ b/tests/transform_intralinks_backticked/src/main.rs @@ -8,10 +8,10 @@ //! //! [![BestStruct doc](https://example.com/image.png)](`crate::foo::BestStruct`) -mod amodule {} +pub mod amodule {} -mod foo { - struct BestStruct {} +pub mod foo { + pub struct BestStruct {} } fn main() {} diff --git a/tests/transform_intralinks_external_crate/Cargo.toml b/tests/transform_intralinks_external_crate/Cargo.toml new file mode 100644 index 0000000..d5223aa --- /dev/null +++ b/tests/transform_intralinks_external_crate/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "integration_test" +version = "0.1.0" +edition = "2021" + +[dependencies] +tower-service = "=0.3.2" diff --git a/tests/transform_intralinks_external_crate/README-expected.md b/tests/transform_intralinks_external_crate/README-expected.md new file mode 100644 index 0000000..311a36a --- /dev/null +++ b/tests/transform_intralinks_external_crate/README-expected.md @@ -0,0 +1,5 @@ + + +This [crate](https://docs.rs/integration_test/latest/integration_test/) links to [`tower_service::Service`](https://docs.rs/tower_service/latest/tower_service/trait.Service.html). + + diff --git a/tests/transform_intralinks_external_crate/README-template.md b/tests/transform_intralinks_external_crate/README-template.md new file mode 100644 index 0000000..3c52daa --- /dev/null +++ b/tests/transform_intralinks_external_crate/README-template.md @@ -0,0 +1 @@ + diff --git a/tests/transform_intralinks_external_crate/src/main.rs b/tests/transform_intralinks_external_crate/src/main.rs new file mode 100644 index 0000000..6f7c567 --- /dev/null +++ b/tests/transform_intralinks_external_crate/src/main.rs @@ -0,0 +1,3 @@ +//! This [crate](crate) links to [`tower_service::Service`](tower_service::Service). + +fn main() {} diff --git a/tests/transform_intralinks_impl_items/README-expected.md b/tests/transform_intralinks_impl_items/README-expected.md index 827fd6f..4a782f1 100644 --- a/tests/transform_intralinks_impl_items/README-expected.md +++ b/tests/transform_intralinks_impl_items/README-expected.md @@ -3,15 +3,16 @@ This crate has [`Foo::new()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.new), [`Foo::a_method()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.a_method), and [`Foo::another_method()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.another_method). -It also has [`Foo::no_self()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.no_self). There's also [`Bar::beer()`](https://docs.rs/integration_test/latest/integration_test/amod/struct.Bar.html#method.beer). +It also has [`Foo::no_self()`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.no_self). There's also [`Bar::beer()`](https://docs.rs/integration_test/latest/integration_test/amod/struct.Bar.html#method.beer) +in [`Bar`](https://docs.rs/integration_test/latest/integration_test/amod/struct.Bar.html). Struct `Foo` has a [type called `baz`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#associatedtype.Baz) and a -[const called `number`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#associatedconstant.number). +[const called `NUMBER`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#associatedconstant.NUMBER). -We have a function in `FooAlias` [called `hello`](https://docs.rs/integration_test/latest/integration_test/type.FooAlias.html#method.hello). +We have a function in [`FooAlias`](https://docs.rs/integration_test/latest/integration_test/type.FooAlias.html) [called `hello`](https://docs.rs/integration_test/latest/integration_test/struct.Foo.html#method.hello). -And in `MyEnum` we have [called `hey`](https://docs.rs/integration_test/latest/integration_test/enum.MyEnum.html#method.hey). +And in `MyEnum` we have a method [called `hey`](https://docs.rs/integration_test/latest/integration_test/enum.MyEnum.html#method.hey). -And in `MyUnion` we have [called `sup`](https://docs.rs/integration_test/latest/integration_test/union.MyUnion.html#method.sup). +And in `MyUnion` we have a method [called `sup`](https://docs.rs/integration_test/latest/integration_test/union.MyUnion.html#method.sup). diff --git a/tests/transform_intralinks_impl_items/src/main.rs b/tests/transform_intralinks_impl_items/src/main.rs index 34f7842..4e6f4ea 100644 --- a/tests/transform_intralinks_impl_items/src/main.rs +++ b/tests/transform_intralinks_impl_items/src/main.rs @@ -1,24 +1,26 @@ //! This crate has [`Foo::new()`](`crate::Foo::new`), [`Foo::a_method()`](`crate::Foo::a_method`), //! and [`Foo::another_method()`](`crate::Foo::another_method`). //! -//! It also has [`Foo::no_self()`](`crate::Foo::no_self`). There's also [`Bar::beer()`](`crate::amod::Bar::beer`). +//! It also has [`Foo::no_self()`](`crate::Foo::no_self`). There's also [`Bar::beer()`](`crate::amod::Bar::beer`) +//! in [`Bar`](`crate::amod::Bar`). //! //! Struct `Foo` has a [type called `baz`](`crate::Foo::Baz`) and a -//! [const called `number`](`crate::Foo::number`). +//! [const called `NUMBER`](`crate::Foo::NUMBER`). //! -//! We have a function in `FooAlias` [called `hello`](`crate::FooAlias::hello`). +//! We have a function in [`FooAlias`](`crate::FooAlias`) [called `hello`](`crate::FooAlias::hello`). //! -//! And in `MyEnum` we have [called `hey`](`crate::MyEnum::hey`). +//! And in `MyEnum` we have a method [called `hey`](`crate::MyEnum::hey`). //! -//! And in `MyUnion` we have [called `sup`](`crate::MyUnion::sup`). +//! And in `MyUnion` we have a method [called `sup`](`crate::MyUnion::sup`). -// TODO Remove this when inherent_associated_types stabilizes. +// TODO Remove this when inherent_associated_types stabilizes (https://github.com/rust-lang/rust/issues/8995). +// This is what allows the `type` definition in `impl Foo`. #![feature(inherent_associated_types)] pub struct Foo {} impl Foo { - const number: usize = 1234; + const NUMBER: usize = 1234; type Baz = u32; @@ -61,7 +63,9 @@ impl MyEnum { fn hey(&self) {} } -union MyUnion {} +union MyUnion { + x: u32, +} impl MyUnion { fn sup(&self) {} diff --git a/tests/transform_intralinks_item_path_collision/Cargo.toml b/tests/transform_intralinks_item_path_collision/Cargo.toml new file mode 100644 index 0000000..ecf8964 --- /dev/null +++ b/tests/transform_intralinks_item_path_collision/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "integration_test" +version = "0.1.0" +edition = "2021" diff --git a/tests/transform_intralinks_item_path_collision/README-expected.md b/tests/transform_intralinks_item_path_collision/README-expected.md new file mode 100644 index 0000000..1af8496 --- /dev/null +++ b/tests/transform_intralinks_item_path_collision/README-expected.md @@ -0,0 +1,9 @@ + + +* [struct foo::AStruct](https://docs.rs/integration_test/latest/integration_test/foo/struct.AStruct.html) +* [macro foo::foo!](https://docs.rs/integration_test/latest/integration_test/macro.foo.html) +* This is ambiguous and should be striped: mod bar +* [mod bar](https://docs.rs/integration_test/latest/integration_test/bar/) +* [macro bar!](https://docs.rs/integration_test/latest/integration_test/macro.bar.html) + + diff --git a/tests/transform_intralinks_item_path_collision/README-template.md b/tests/transform_intralinks_item_path_collision/README-template.md new file mode 100644 index 0000000..10d0ca4 --- /dev/null +++ b/tests/transform_intralinks_item_path_collision/README-template.md @@ -0,0 +1,2 @@ + + diff --git a/tests/transform_intralinks_item_path_collision/src/lib.rs b/tests/transform_intralinks_item_path_collision/src/lib.rs new file mode 100644 index 0000000..b032dac --- /dev/null +++ b/tests/transform_intralinks_item_path_collision/src/lib.rs @@ -0,0 +1,23 @@ +//! * [struct foo::AStruct](foo::AStruct) +//! * [macro foo::foo!](foo::foo) +//! * This is ambiguous and should be striped: [mod bar](bar) +//! * [mod bar](mod@bar) +//! * [macro bar!](bar!) + +#[macro_export] +macro_rules! bar { + () => {{}}; +} + +pub mod bar {} + +pub mod foo { + #[macro_export] + macro_rules! foo { + () => {{}}; + } + + pub use foo; + + pub struct AStruct {} +} diff --git a/tests/transform_intralinks_module_walk/src/amodule.rs b/tests/transform_intralinks_module_walk/src/amodule.rs deleted file mode 100644 index 8d183db..0000000 --- a/tests/transform_intralinks_module_walk/src/amodule.rs +++ /dev/null @@ -1 +0,0 @@ -trait T {} diff --git a/tests/transform_intralinks_module_walk/src/foo/bar.rs b/tests/transform_intralinks_module_walk/src/foo/bar.rs deleted file mode 100644 index 5f0fd8f..0000000 --- a/tests/transform_intralinks_module_walk/src/foo/bar.rs +++ /dev/null @@ -1 +0,0 @@ -struct S1 {} diff --git a/tests/transform_intralinks_module_walk/src/foo/mod.rs b/tests/transform_intralinks_module_walk/src/foo/mod.rs deleted file mode 100644 index 0c9b256..0000000 --- a/tests/transform_intralinks_module_walk/src/foo/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -struct S0 {} - -mod bar; diff --git a/tests/transform_intralinks_multiple_modules/Cargo.toml b/tests/transform_intralinks_multiple_modules/Cargo.toml new file mode 100644 index 0000000..ecf8964 --- /dev/null +++ b/tests/transform_intralinks_multiple_modules/Cargo.toml @@ -0,0 +1,4 @@ +[package] +name = "integration_test" +version = "0.1.0" +edition = "2021" diff --git a/tests/transform_intralinks_module_walk/README-expected.md b/tests/transform_intralinks_multiple_modules/README-expected.md similarity index 100% rename from tests/transform_intralinks_module_walk/README-expected.md rename to tests/transform_intralinks_multiple_modules/README-expected.md diff --git a/tests/transform_intralinks_multiple_modules/README-template.md b/tests/transform_intralinks_multiple_modules/README-template.md new file mode 100644 index 0000000..10d0ca4 --- /dev/null +++ b/tests/transform_intralinks_multiple_modules/README-template.md @@ -0,0 +1,2 @@ + + diff --git a/tests/transform_intralinks_multiple_modules/src/amodule.rs b/tests/transform_intralinks_multiple_modules/src/amodule.rs new file mode 100644 index 0000000..16e8fbc --- /dev/null +++ b/tests/transform_intralinks_multiple_modules/src/amodule.rs @@ -0,0 +1 @@ +pub trait T {} diff --git a/tests/transform_intralinks_multiple_modules/src/foo/bar.rs b/tests/transform_intralinks_multiple_modules/src/foo/bar.rs new file mode 100644 index 0000000..60b4f81 --- /dev/null +++ b/tests/transform_intralinks_multiple_modules/src/foo/bar.rs @@ -0,0 +1 @@ +pub struct S1 {} diff --git a/tests/transform_intralinks_multiple_modules/src/foo/mod.rs b/tests/transform_intralinks_multiple_modules/src/foo/mod.rs new file mode 100644 index 0000000..c848ca0 --- /dev/null +++ b/tests/transform_intralinks_multiple_modules/src/foo/mod.rs @@ -0,0 +1,3 @@ +pub struct S0 {} + +pub mod bar; diff --git a/tests/transform_intralinks_module_walk/src/main.rs b/tests/transform_intralinks_multiple_modules/src/main.rs similarity index 86% rename from tests/transform_intralinks_module_walk/src/main.rs rename to tests/transform_intralinks_multiple_modules/src/main.rs index 88d30a8..926807a 100644 --- a/tests/transform_intralinks_module_walk/src/main.rs +++ b/tests/transform_intralinks_multiple_modules/src/main.rs @@ -5,7 +5,7 @@ //! [foo bar](crate::foo::bar) //! [foo bar S1](crate::foo::bar::S1) -mod amodule; -mod foo; +pub mod amodule; +pub mod foo; fn main() {} diff --git a/tests/transform_intralinks_package_and_crate_names/Cargo.toml b/tests/transform_intralinks_package_and_crate_names/Cargo.toml new file mode 100644 index 0000000..dd78a44 --- /dev/null +++ b/tests/transform_intralinks_package_and_crate_names/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "integration_test" +version = "0.1.0" +edition = "2021" + +[lib] +name = "the_lib" +path = "src/lib.rs" diff --git a/tests/transform_intralinks_package_and_crate_names/README-expected.md b/tests/transform_intralinks_package_and_crate_names/README-expected.md new file mode 100644 index 0000000..2f4bc1f --- /dev/null +++ b/tests/transform_intralinks_package_and_crate_names/README-expected.md @@ -0,0 +1,5 @@ + + +This crate contains the function [`foo()`](https://docs.rs/integration_test/latest/the_lib/fn.foo.html). + + diff --git a/tests/transform_intralinks_package_and_crate_names/README-template.md b/tests/transform_intralinks_package_and_crate_names/README-template.md new file mode 100644 index 0000000..3c52daa --- /dev/null +++ b/tests/transform_intralinks_package_and_crate_names/README-template.md @@ -0,0 +1 @@ + diff --git a/tests/transform_intralinks_package_and_crate_names/src/lib.rs b/tests/transform_intralinks_package_and_crate_names/src/lib.rs new file mode 100644 index 0000000..94b9b4d --- /dev/null +++ b/tests/transform_intralinks_package_and_crate_names/src/lib.rs @@ -0,0 +1,3 @@ +//! This crate contains the function [`foo()`](foo). + +pub fn foo() {} diff --git a/tests/transform_intralinks_procedural_macros/Cargo.toml b/tests/transform_intralinks_procedural_macros/Cargo.toml new file mode 100644 index 0000000..c436456 --- /dev/null +++ b/tests/transform_intralinks_procedural_macros/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "integration_test" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true diff --git a/tests/transform_intralinks_procedural_macros/README-expected.md b/tests/transform_intralinks_procedural_macros/README-expected.md new file mode 100644 index 0000000..62d11d3 --- /dev/null +++ b/tests/transform_intralinks_procedural_macros/README-expected.md @@ -0,0 +1,8 @@ + + +Procedural macro item kinds: + +* [DeriveFn](https://docs.rs/integration_test/latest/integration_test/derive.DeriveFn.html) +* [an_attribute](https://docs.rs/integration_test/latest/integration_test/attr.an_attribute.html) + + diff --git a/tests/transform_intralinks_procedural_macros/README-template.md b/tests/transform_intralinks_procedural_macros/README-template.md new file mode 100644 index 0000000..10d0ca4 --- /dev/null +++ b/tests/transform_intralinks_procedural_macros/README-template.md @@ -0,0 +1,2 @@ + + diff --git a/tests/transform_intralinks_procedural_macros/src/lib.rs b/tests/transform_intralinks_procedural_macros/src/lib.rs new file mode 100644 index 0000000..00dca92 --- /dev/null +++ b/tests/transform_intralinks_procedural_macros/src/lib.rs @@ -0,0 +1,16 @@ +//! Procedural macro item kinds: +//! +//! * [DeriveFn](DeriveFn) +//! * [an_attribute](macro@an_attribute) + +extern crate proc_macro; + +#[proc_macro_derive(DeriveFn)] +pub fn derive_fn(_item: proc_macro::TokenStream) -> proc_macro::TokenStream { + "fn foo() -> u32 { 42 }".parse().unwrap() +} + +#[proc_macro_attribute] +pub fn an_attribute(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream { + item +} diff --git a/tests/transform_intralinks_reference_links/README-expected.md b/tests/transform_intralinks_reference_links/README-expected.md index 9fe5396..a7085b5 100644 --- a/tests/transform_intralinks_reference_links/README-expected.md +++ b/tests/transform_intralinks_reference_links/README-expected.md @@ -6,7 +6,7 @@ This [beautiful crate] is cool because it contains [modules] and some other [stuff] as well. This link is broken and this is not supported, -but this should [wor\\k \[fi\]ne]. +but this should wor\\k \[fi\]ne. Go ahead and check all the [structs in foo] specifically [this one]. Also, this is a nice function: [copy][cp]. @@ -16,7 +16,6 @@ Go ahead and check all the [structs in foo] specifically [beautiful crate]: https://docs.rs/integration_test/latest/integration_test/ [modules]: https://docs.rs/integration_test/latest/integration_test/amodule/ [stuff]: https://en.wikipedia.org/wiki/Stuff -[wor\\k \[fi\]ne]: f\\i\(n\)e [structs in foo]: https://docs.rs/integration_test/latest/integration_test/foo/#structs [this one]: https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html [cp]: https://doc.rust-lang.org/stable/std/fs/fn.copy.html#examples "A title here" diff --git a/tests/transform_intralinks_reference_links/src/main.rs b/tests/transform_intralinks_reference_links/src/main.rs index 0c0f78d..8429764 100644 --- a/tests/transform_intralinks_reference_links/src/main.rs +++ b/tests/transform_intralinks_reference_links/src/main.rs @@ -21,10 +21,10 @@ //! [BestStruct doc]: https://example.com/image.png //! [BestStruct]: crate::foo::BestStruct -mod amodule {} +pub mod amodule {} -mod foo { - struct BestStruct {} +pub mod foo { + pub struct BestStruct {} } fn main() {} diff --git a/tests/transform_intralinks_simple/README-expected.md b/tests/transform_intralinks_simple/README-expected.md index 2bfe33b..f4ba26b 100644 --- a/tests/transform_intralinks_simple/README-expected.md +++ b/tests/transform_intralinks_simple/README-expected.md @@ -5,7 +5,7 @@ This will not be [modified](crate::amodule). This [beautiful crate](https://docs.rs/integration_test/latest/integration_test/) is cool because it contains [modules](https://docs.rs/integration_test/latest/integration_test/amodule/) and some other [stuff](https://en.wikipedia.org/wiki/Stuff) as well. -This link is broken, but this should [wor\\k \[ju\]st](f\\i\(n\)e). +This link is broken, but this should wor\\k \[ju\]st. Go ahead and check all the [structs in foo](https://docs.rs/integration_test/latest/integration_test/foo/#structs) specifically [this one](https://docs.rs/integration_test/latest/integration_test/foo/struct.BestStruct.html) diff --git a/tests/transform_intralinks_simple/src/main.rs b/tests/transform_intralinks_simple/src/main.rs index 175f8e5..5f89096 100644 --- a/tests/transform_intralinks_simple/src/main.rs +++ b/tests/transform_intralinks_simple/src/main.rs @@ -8,10 +8,10 @@ //! //! [![BestStruct doc](https://example.com/image.png)](crate::foo::BestStruct) -mod amodule {} +pub mod amodule {} -mod foo { - struct BestStruct {} +pub mod foo { + pub struct BestStruct {} } fn main() {} diff --git a/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/Cargo.toml b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/Cargo.toml new file mode 100644 index 0000000..e290306 --- /dev/null +++ b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "integration_test" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "test-precondition" +path = "src/test-precondition-main.rs" + +[[bin]] +name = "real-test" +path = "src/main.rs" diff --git a/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/README-expected.md b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/README-expected.md new file mode 100644 index 0000000..5ca4119 --- /dev/null +++ b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/README-expected.md @@ -0,0 +1,8 @@ + + +This crate contains no [intralinks](https://en.wikipedia.org/wiki/Stuff) so rustdoc should not +run. We test that by having a broken rust file that rustdoc would not [accept]. + +[accept]: https://en.wikipedia.org/wiki/Stuff + + diff --git a/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/README-template.md b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/README-template.md new file mode 100644 index 0000000..10d0ca4 --- /dev/null +++ b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/README-template.md @@ -0,0 +1,2 @@ + + diff --git a/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/src/main.rs b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/src/main.rs new file mode 100644 index 0000000..0588275 --- /dev/null +++ b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/src/main.rs @@ -0,0 +1,12 @@ +//! This crate contains no [intralinks](https://en.wikipedia.org/wiki/Stuff) so rustdoc should not +//! run. We test that by having a broken rust file that rustdoc would not [accept]. +//! +//! [accept]: https://en.wikipedia.org/wiki/Stuff + +#![deny(rustdoc::broken_intra_doc_links)] + +mod no_module_here; + +fn main() { + +} diff --git a/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/src/test-precondition-main.rs b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/src/test-precondition-main.rs new file mode 100644 index 0000000..d35a3f6 --- /dev/null +++ b/tests/transform_intralinks_skip_rustdoc_when_no_intralinks/src/test-precondition-main.rs @@ -0,0 +1,10 @@ +//! Crate with an [`main`](main) to prove that rustdoc fails with this. Otherwise the test is not +//! testing that rustdoc is not running. + +#![deny(rustdoc::broken_intra_doc_links)] + +mod no_module_here; + +fn main() { + +} diff --git a/tests/transform_intralinks_stdlib_links/README-expected.md b/tests/transform_intralinks_stdlib_links/README-expected.md index 81abd97..2cc6e33 100644 --- a/tests/transform_intralinks_stdlib_links/README-expected.md +++ b/tests/transform_intralinks_stdlib_links/README-expected.md @@ -5,6 +5,5 @@ [copy](https://doc.rust-lang.org/stable/std/fs/fn.copy.html) broken [std](https://doc.rust-lang.org/stable/std/) -[alloc](https://doc.rust-lang.org/stable/alloc/) diff --git a/tests/transform_intralinks_stdlib_links/src/main.rs b/tests/transform_intralinks_stdlib_links/src/main.rs index 80c0cb6..d8b86ae 100644 --- a/tests/transform_intralinks_stdlib_links/src/main.rs +++ b/tests/transform_intralinks_stdlib_links/src/main.rs @@ -1,8 +1,7 @@ -//! [Vec](::alloc::vec::Vec) +//! [Vec](::std::vec::Vec) //! [collections](::std::collections) //! [copy](::std::fs::copy) //! [broken](::foo::bar) //! [std](::std) -//! [alloc](::alloc) fn main() {}