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