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