forc_doc/doc/
mod.rs

1//! Handles conversion of compiled typed Sway programs into [Document]s that can be rendered into HTML.
2mod descriptor;
3pub mod module;
4
5use crate::{
6    doc::{descriptor::Descriptor, module::ModuleInfo},
7    render::{
8        item::{components::*, context::DocImplTrait, documentable_type::DocumentableType},
9        link::DocLink,
10        util::{
11            format::docstring::{create_preview, DocStrings},
12            strip_generic_suffix,
13        },
14    },
15};
16use anyhow::Result;
17use rayon::prelude::*;
18use std::{
19    collections::HashMap,
20    ops::{Deref, DerefMut},
21    option::Option,
22};
23use sway_core::{
24    decl_engine::DeclEngine,
25    language::ty::{TyAstNodeContent, TyDecl, TyImplSelfOrTrait, TyModule, TyProgram, TySubmodule},
26    Engines,
27};
28use sway_features::ExperimentalFeatures;
29use sway_types::{BaseIdent, Spanned};
30
31#[derive(Default, Clone)]
32pub struct Documentation(pub Vec<Document>);
33
34impl Documentation {
35    /// Gather [Documentation] from the [TyProgram].
36    pub fn from_ty_program(
37        engines: &Engines,
38        project_name: &str,
39        typed_program: &TyProgram,
40        document_private_items: bool,
41        experimental: ExperimentalFeatures,
42    ) -> Result<Documentation> {
43        // the first module prefix will always be the project name
44        let mut docs = Documentation::default();
45        let mut impl_traits: Vec<(TyImplSelfOrTrait, ModuleInfo)> = Vec::new();
46        let module_info = ModuleInfo::from_ty_module(vec![project_name.to_owned()], None);
47        Documentation::from_ty_module(
48            engines.de(),
49            &module_info,
50            &typed_program.root_module,
51            &mut docs,
52            &mut impl_traits,
53            document_private_items,
54            experimental,
55        )?;
56
57        // this is the same process as before but for submodules
58        for (_, ref typed_submodule) in &typed_program.root_module.submodules {
59            let attributes = (!typed_submodule.module.attributes.is_empty())
60                .then(|| typed_submodule.module.attributes.to_html_string());
61            let module_prefix =
62                ModuleInfo::from_ty_module(vec![project_name.to_owned()], attributes);
63            Documentation::from_ty_submodule(
64                engines.de(),
65                typed_submodule,
66                &mut docs,
67                &mut impl_traits,
68                &module_prefix,
69                document_private_items,
70                experimental,
71            )?;
72        }
73        let trait_decls = docs
74            .iter()
75            .filter_map(|d| {
76                (d.item_header.friendly_name == "trait").then_some((
77                    d.item_header.item_name.clone(),
78                    d.item_header.module_info.clone(),
79                ))
80            })
81            .collect::<HashMap<BaseIdent, ModuleInfo>>();
82
83        // Add one documentation page for each primitive type that has an implementation.
84        let primitive_docs: Vec<_> = impl_traits
85            .par_iter()
86            .filter_map(|(impl_trait, module_info)| {
87                let impl_for_type = engines.te().get(impl_trait.implementing_for.type_id());
88                if let Ok(Descriptor::Documentable(doc)) =
89                    Descriptor::from_type_info(impl_for_type.as_ref(), engines, module_info.clone())
90                {
91                    Some(doc)
92                } else {
93                    None
94                }
95            })
96            .collect();
97
98        // Add unique primitive docs
99        for doc in primitive_docs {
100            if !docs.contains(&doc) {
101                docs.push(doc);
102            }
103        }
104
105        // match for the spans to add the impl_traits to their corresponding doc:
106        // currently this compares the spans as str, but this needs to change
107        // to compare the actual types
108        for doc in docs.iter_mut() {
109            let mut impl_trait_vec: Vec<DocImplTrait> = Vec::new();
110            let mut inherent_impl_vec: Vec<DocImplTrait> = Vec::new();
111
112            // Check for implementations of the current struct/enum/primitive.
113            match doc.item_body.ty {
114                DocumentableType::Declared(TyDecl::StructDecl(_))
115                | DocumentableType::Declared(TyDecl::EnumDecl(_))
116                | DocumentableType::Primitive(_) => {
117                    let item_name = &doc.item_header.item_name;
118                    for (impl_trait, _) in impl_traits.iter_mut() {
119                        // Check if this implementation is for this struct/enum.
120                        if item_name.as_str()
121                            == strip_generic_suffix(impl_trait.implementing_for.span().as_str())
122                        {
123                            let module_info_override = if let Some(decl_module_info) =
124                                trait_decls.get(&impl_trait.trait_name.suffix)
125                            {
126                                Some(decl_module_info.module_prefixes.clone())
127                            } else {
128                                impl_trait.trait_name = impl_trait
129                                    .trait_name
130                                    .to_canonical_path(engines, &typed_program.namespace);
131                                None
132                            };
133
134                            let doc_impl_trait = DocImplTrait {
135                                impl_for_module: doc.module_info.clone(),
136                                impl_trait: impl_trait.clone(),
137                                module_info_override,
138                            };
139
140                            if doc_impl_trait.is_inherent() {
141                                inherent_impl_vec.push(doc_impl_trait);
142                            } else {
143                                impl_trait_vec.push(doc_impl_trait);
144                            }
145                        }
146                    }
147                }
148                _ => {}
149            }
150
151            if !impl_trait_vec.is_empty() {
152                doc.item_body.item_context.impl_traits = Some(impl_trait_vec);
153            }
154
155            if !inherent_impl_vec.is_empty() {
156                doc.item_body.item_context.inherent_impls = Some(inherent_impl_vec);
157            }
158        }
159
160        Ok(docs)
161    }
162    fn from_ty_module(
163        decl_engine: &DeclEngine,
164        module_info: &ModuleInfo,
165        ty_module: &TyModule,
166        docs: &mut Documentation,
167        impl_traits: &mut Vec<(TyImplSelfOrTrait, ModuleInfo)>,
168        document_private_items: bool,
169        experimental: ExperimentalFeatures,
170    ) -> Result<()> {
171        let results: Result<Vec<_>, anyhow::Error> = ty_module
172            .all_nodes
173            .par_iter()
174            .filter_map(|ast_node| {
175                if let TyAstNodeContent::Declaration(ref decl) = ast_node.content {
176                    Some(decl)
177                } else {
178                    None
179                }
180            })
181            .map(|decl| {
182                if let TyDecl::ImplSelfOrTrait(impl_trait) = decl {
183                    let impl_data = (
184                        (*decl_engine.get_impl_self_or_trait(&impl_trait.decl_id)).clone(),
185                        module_info.clone(),
186                    );
187                    Ok((Some(impl_data), None))
188                } else {
189                    let desc = Descriptor::from_typed_decl(
190                        decl_engine,
191                        decl,
192                        module_info.clone(),
193                        document_private_items,
194                        experimental,
195                    )?;
196
197                    let doc = match desc {
198                        Descriptor::Documentable(doc) => Some(doc),
199                        Descriptor::NonDocumentable => None,
200                    };
201                    Ok((None, doc))
202                }
203            })
204            .collect();
205
206        for (impl_trait_opt, doc_opt) in results? {
207            if let Some(impl_trait) = impl_trait_opt {
208                impl_traits.push(impl_trait);
209            }
210            if let Some(doc) = doc_opt {
211                docs.push(doc);
212            }
213        }
214
215        Ok(())
216    }
217    fn from_ty_submodule(
218        decl_engine: &DeclEngine,
219        typed_submodule: &TySubmodule,
220        docs: &mut Documentation,
221        impl_traits: &mut Vec<(TyImplSelfOrTrait, ModuleInfo)>,
222        module_info: &ModuleInfo,
223        document_private_items: bool,
224        experimental: ExperimentalFeatures,
225    ) -> Result<()> {
226        let mut module_info = module_info.to_owned();
227        module_info
228            .module_prefixes
229            .push(typed_submodule.mod_name_span.as_str().to_owned());
230        Documentation::from_ty_module(
231            decl_engine,
232            &module_info.clone(),
233            &typed_submodule.module,
234            docs,
235            impl_traits,
236            document_private_items,
237            experimental,
238        )?;
239
240        for (_, submodule) in &typed_submodule.module.submodules {
241            Documentation::from_ty_submodule(
242                decl_engine,
243                submodule,
244                docs,
245                impl_traits,
246                &module_info,
247                document_private_items,
248                experimental,
249            )?;
250        }
251
252        Ok(())
253    }
254}
255
256/// A finalized Document ready to be rendered. We want to retain all
257/// information including spans, fields on structs, variants on enums etc.
258#[derive(Clone, Debug)]
259pub struct Document {
260    pub module_info: ModuleInfo,
261    pub item_header: ItemHeader,
262    pub item_body: ItemBody,
263    pub raw_attributes: Option<String>,
264}
265
266impl Document {
267    /// Creates an HTML file name from the [Document].
268    pub fn html_filename(&self) -> String {
269        use sway_core::language::ty::TyDecl::StorageDecl;
270        let name = match &self.item_body.ty {
271            &DocumentableType::Declared(StorageDecl { .. }) => None,
272            _ => Some(self.item_header.item_name.as_str()),
273        };
274
275        Document::create_html_filename(self.item_body.ty.doc_name(), name)
276    }
277    fn create_html_filename(ty: &str, name: Option<&str>) -> String {
278        match name {
279            Some(name) => format!("{ty}.{name}.html"),
280            None => {
281                format!("{ty}.html") // storage does not have an Ident
282            }
283        }
284    }
285    /// Generate link info used in navigation between docs.
286    pub fn link(&self) -> DocLink {
287        DocLink {
288            name: self.item_header.item_name.as_str().to_owned(),
289            module_info: self.module_info.clone(),
290            html_filename: self.html_filename(),
291            preview_opt: self.preview_opt(),
292        }
293    }
294    pub fn preview_opt(&self) -> Option<String> {
295        create_preview(self.raw_attributes.clone())
296    }
297}
298
299impl PartialEq for Document {
300    fn eq(&self, other: &Self) -> bool {
301        self.item_header.item_name == other.item_header.item_name
302            && self.item_header.module_info.module_prefixes
303                == other.item_header.module_info.module_prefixes
304    }
305}
306
307impl Deref for Documentation {
308    type Target = Vec<Document>;
309    fn deref(&self) -> &Self::Target {
310        &self.0
311    }
312}
313
314impl DerefMut for Documentation {
315    fn deref_mut(&mut self) -> &mut Self::Target {
316        &mut self.0
317    }
318}