forc_doc/
lib.rs

1pub mod cli;
2pub mod doc;
3pub mod render;
4pub mod search;
5pub mod tests;
6
7use anyhow::{bail, Result};
8use cli::Command;
9use doc::Documentation;
10use forc_pkg as pkg;
11use forc_pkg::{
12    manifest::{GenericManifestFile, ManifestFile},
13    PackageManifestFile,
14};
15use forc_tracing::println_action_green;
16use forc_util::default_output_directory;
17use render::RenderedDocumentation;
18use std::sync::Arc;
19use std::{
20    fs,
21    path::{Path, PathBuf},
22};
23use sway_core::{language::ty::TyProgram, BuildTarget, Engines};
24use sway_features::ExperimentalFeatures;
25
26pub const ASSETS_DIR_NAME: &str = "static.files";
27
28/// Information passed to the render phase to get TypeInfo, CallPath or visibility for type anchors.
29#[derive(Clone)]
30pub struct RenderPlan<'e> {
31    no_deps: bool,
32    document_private_items: bool,
33    engines: &'e Engines,
34}
35
36impl<'e> RenderPlan<'e> {
37    pub fn new(
38        no_deps: bool,
39        document_private_items: bool,
40        engines: &'e Engines,
41    ) -> RenderPlan<'e> {
42        Self {
43            no_deps,
44            document_private_items,
45            engines,
46        }
47    }
48}
49
50pub struct ProgramInfo<'a> {
51    pub ty_program: Arc<TyProgram>,
52    pub engines: &'a Engines,
53    pub manifest: &'a ManifestFile,
54    pub pkg_manifest: &'a PackageManifestFile,
55}
56
57pub fn compile_html(
58    build_instructions: &Command,
59    get_doc_dir: &dyn Fn(&Command) -> String,
60) -> Result<(PathBuf, Box<PackageManifestFile>)> {
61    // get manifest directory
62    let dir = if let Some(ref path) = build_instructions.path {
63        PathBuf::from(path)
64    } else {
65        std::env::current_dir()?
66    };
67    let manifest = ManifestFile::from_dir(dir)?;
68    let ManifestFile::Package(pkg_manifest) = &manifest else {
69        bail!("forc-doc does not support workspaces.")
70    };
71
72    // create doc path
73    let out_path = default_output_directory(manifest.dir());
74    let doc_dir = get_doc_dir(build_instructions);
75    let doc_path = out_path.join(doc_dir);
76    if doc_path.exists() {
77        std::fs::remove_dir_all(&doc_path)?;
78    }
79    fs::create_dir_all(&doc_path)?;
80
81    println_action_green(
82        "Compiling",
83        &format!(
84            "{} ({})",
85            pkg_manifest.project_name(),
86            manifest.dir().to_string_lossy()
87        ),
88    );
89
90    let member_manifests = manifest.member_manifests()?;
91    let lock_path = manifest.lock_path()?;
92
93    let ipfs_node = build_instructions.ipfs_node.clone().unwrap_or_default();
94    let plan = pkg::BuildPlan::from_lock_and_manifests(
95        &lock_path,
96        &member_manifests,
97        build_instructions.locked,
98        build_instructions.offline,
99        &ipfs_node,
100    )?;
101
102    let engines = Engines::default();
103    let tests_enabled = build_instructions.document_private_items;
104    let mut compile_results = pkg::check(
105        &plan,
106        BuildTarget::default(),
107        build_instructions.silent,
108        None,
109        tests_enabled,
110        &engines,
111        None,
112        &build_instructions.experimental.experimental,
113        &build_instructions.experimental.no_experimental,
114        sway_core::DbgGeneration::Full,
115    )?;
116
117    let raw_docs = if build_instructions.no_deps {
118        let Some(ty_program) = compile_results
119            .pop()
120            .and_then(|(programs, _handler)| programs)
121            .and_then(|p| p.typed.ok())
122        else {
123            bail! {
124                "documentation could not be built from manifest located at '{}'",
125                pkg_manifest.path().display()
126            }
127        };
128        let program_info = ProgramInfo {
129            ty_program,
130            engines: &engines,
131            manifest: &manifest,
132            pkg_manifest,
133        };
134        build_docs(program_info, &doc_path, build_instructions)?
135    } else {
136        let order = plan.compilation_order();
137        let graph = plan.graph();
138        let manifest_map = plan.manifest_map();
139        let mut raw_docs = Documentation(Vec::new());
140
141        for (node, (compile_result, _handler)) in order.iter().zip(compile_results) {
142            let id = &graph[*node].id();
143            if let Some(pkg_manifest_file) = manifest_map.get(id) {
144                let manifest_file = ManifestFile::from_dir(pkg_manifest_file.path())?;
145                let Some(ty_program) = compile_result.and_then(|programs| programs.typed.ok())
146                else {
147                    bail!(
148                        "documentation could not be built from manifest located at '{}'",
149                        pkg_manifest_file.path().display()
150                    )
151                };
152                let program_info = ProgramInfo {
153                    ty_program,
154                    engines: &engines,
155                    manifest: &manifest_file,
156                    pkg_manifest: pkg_manifest_file,
157                };
158                raw_docs
159                    .0
160                    .extend(build_docs(program_info, &doc_path, build_instructions)?.0);
161            }
162        }
163        raw_docs
164    };
165    search::write_search_index(&doc_path, &raw_docs)?;
166
167    Ok((doc_path, pkg_manifest.to_owned()))
168}
169
170fn build_docs(
171    program_info: ProgramInfo,
172    doc_path: &Path,
173    build_instructions: &Command,
174) -> Result<Documentation> {
175    let Command {
176        document_private_items,
177        no_deps,
178        experimental,
179        ..
180    } = build_instructions;
181    let ProgramInfo {
182        ty_program,
183        engines,
184        manifest,
185        pkg_manifest,
186    } = program_info;
187
188    let experimental = ExperimentalFeatures::new(
189        &pkg_manifest.project.experimental,
190        &experimental.experimental,
191        &experimental.no_experimental,
192    )
193    .map_err(|err| anyhow::anyhow!("{err}"))?;
194
195    println_action_green(
196        "Building",
197        &format!(
198            "documentation for {} ({})",
199            pkg_manifest.project_name(),
200            manifest.dir().to_string_lossy()
201        ),
202    );
203
204    let raw_docs = Documentation::from_ty_program(
205        engines,
206        pkg_manifest.project_name(),
207        &ty_program,
208        *document_private_items,
209        experimental,
210    )?;
211    let root_attributes = (!ty_program.root_module.attributes.is_empty())
212        .then_some(ty_program.root_module.attributes.clone());
213    let forc_version = pkg_manifest
214        .project
215        .forc_version
216        .as_ref()
217        .map(|ver| format!("Forc v{}.{}.{}", ver.major, ver.minor, ver.patch));
218    // render docs to HTML
219    let rendered_docs = RenderedDocumentation::from_raw_docs(
220        raw_docs.clone(),
221        RenderPlan::new(*no_deps, *document_private_items, engines),
222        root_attributes,
223        &ty_program.kind,
224        forc_version,
225    )?;
226
227    // write file contents to doc folder
228    write_content(rendered_docs, doc_path)?;
229    println_action_green("Finished", pkg_manifest.project_name());
230
231    Ok(raw_docs)
232}
233
234fn write_content(rendered_docs: RenderedDocumentation, doc_path: &Path) -> Result<()> {
235    for doc in rendered_docs.0 {
236        let mut doc_path = doc_path.to_path_buf();
237        for prefix in doc.module_info.module_prefixes {
238            doc_path.push(prefix);
239        }
240        fs::create_dir_all(&doc_path)?;
241        doc_path.push(doc.html_filename);
242        fs::write(&doc_path, doc.file_contents.0.as_bytes())?;
243    }
244    Ok(())
245}
246
247const DOC_DIR_NAME: &str = "doc";
248pub fn get_doc_dir(_build_instructions: &Command) -> String {
249    DOC_DIR_NAME.into()
250}