forc_doc/doc/
mod.rs

1//! Handles conversion of compiled typed Sway programs into [Document]s that can be rendered into HTML.
2use crate::{
3    doc::{descriptor::Descriptor, module::ModuleInfo},
4    render::{
5        item::{components::*, context::DocImplTrait, documentable_type::DocumentableType},
6        link::DocLink,
7        util::{
8            format::docstring::{create_preview, DocStrings},
9            strip_generic_suffix,
10        },
11    },
12};
13use anyhow::Result;
14use std::{
15    collections::HashMap,
16    ops::{Deref, DerefMut},
17    option::Option,
18};
19use sway_core::{
20    decl_engine::DeclEngine,
21    language::ty::{TyAstNodeContent, TyDecl, TyImplSelfOrTrait, TyModule, TyProgram, TySubmodule},
22    Engines,
23};
24use sway_features::ExperimentalFeatures;
25use sway_types::BaseIdent;
26use sway_types::Spanned;
27
28mod descriptor;
29pub mod module;
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        for (impl_trait, module_info) in impl_traits.iter() {
85            let impl_for_type = engines.te().get(impl_trait.implementing_for.type_id());
86            if let Ok(Descriptor::Documentable(doc)) =
87                Descriptor::from_type_info(impl_for_type.as_ref(), engines, module_info.clone())
88            {
89                if !docs.iter().any(|existing_doc| *existing_doc == doc) {
90                    docs.push(doc);
91                }
92            }
93        }
94
95        // match for the spans to add the impl_traits to their corresponding doc:
96        // currently this compares the spans as str, but this needs to change
97        // to compare the actual types
98        for doc in docs.iter_mut() {
99            let mut impl_trait_vec: Vec<DocImplTrait> = Vec::new();
100            let mut inherent_impl_vec: Vec<DocImplTrait> = Vec::new();
101
102            // Check for implementations of the current struct/enum/primitive.
103            match doc.item_body.ty {
104                DocumentableType::Declared(TyDecl::StructDecl(_))
105                | DocumentableType::Declared(TyDecl::EnumDecl(_))
106                | DocumentableType::Primitive(_) => {
107                    let item_name = doc.item_header.item_name.clone();
108                    for (impl_trait, _) in impl_traits.iter_mut() {
109                        // Check if this implementation is for this struct/enum.
110                        if item_name.as_str()
111                            == strip_generic_suffix(impl_trait.implementing_for.span().as_str())
112                        {
113                            let module_info_override = if let Some(decl_module_info) =
114                                trait_decls.get(&impl_trait.trait_name.suffix)
115                            {
116                                Some(decl_module_info.module_prefixes.clone())
117                            } else {
118                                impl_trait.trait_name = impl_trait
119                                    .trait_name
120                                    .to_canonical_path(engines, &typed_program.namespace);
121                                None
122                            };
123
124                            let doc_impl_trait = DocImplTrait {
125                                impl_for_module: doc.module_info.clone(),
126                                impl_trait: impl_trait.clone(),
127                                module_info_override,
128                            };
129
130                            if doc_impl_trait.is_inherent() {
131                                inherent_impl_vec.push(doc_impl_trait);
132                            } else {
133                                impl_trait_vec.push(doc_impl_trait);
134                            }
135                        }
136                    }
137                }
138                _ => {}
139            }
140
141            if !impl_trait_vec.is_empty() {
142                doc.item_body.item_context.impl_traits = Some(impl_trait_vec);
143            }
144
145            if !inherent_impl_vec.is_empty() {
146                doc.item_body.item_context.inherent_impls = Some(inherent_impl_vec);
147            }
148        }
149
150        Ok(docs)
151    }
152    fn from_ty_module(
153        decl_engine: &DeclEngine,
154        module_info: &ModuleInfo,
155        ty_module: &TyModule,
156        docs: &mut Documentation,
157        impl_traits: &mut Vec<(TyImplSelfOrTrait, ModuleInfo)>,
158        document_private_items: bool,
159        experimental: ExperimentalFeatures,
160    ) -> Result<()> {
161        for ast_node in &ty_module.all_nodes {
162            if let TyAstNodeContent::Declaration(ref decl) = ast_node.content {
163                if let TyDecl::ImplSelfOrTrait(impl_trait) = decl {
164                    impl_traits.push((
165                        (*decl_engine.get_impl_self_or_trait(&impl_trait.decl_id)).clone(),
166                        module_info.clone(),
167                    ));
168                } else {
169                    let desc = Descriptor::from_typed_decl(
170                        decl_engine,
171                        decl,
172                        module_info.clone(),
173                        document_private_items,
174                        experimental,
175                    )?;
176
177                    if let Descriptor::Documentable(doc) = desc {
178                        docs.push(doc);
179                    }
180                }
181            }
182        }
183
184        Ok(())
185    }
186    fn from_ty_submodule(
187        decl_engine: &DeclEngine,
188        typed_submodule: &TySubmodule,
189        docs: &mut Documentation,
190        impl_traits: &mut Vec<(TyImplSelfOrTrait, ModuleInfo)>,
191        module_info: &ModuleInfo,
192        document_private_items: bool,
193        experimental: ExperimentalFeatures,
194    ) -> Result<()> {
195        let mut module_info = module_info.to_owned();
196        module_info
197            .module_prefixes
198            .push(typed_submodule.mod_name_span.as_str().to_owned());
199        Documentation::from_ty_module(
200            decl_engine,
201            &module_info.clone(),
202            &typed_submodule.module,
203            docs,
204            impl_traits,
205            document_private_items,
206            experimental,
207        )?;
208
209        for (_, submodule) in &typed_submodule.module.submodules {
210            Documentation::from_ty_submodule(
211                decl_engine,
212                submodule,
213                docs,
214                impl_traits,
215                &module_info,
216                document_private_items,
217                experimental,
218            )?;
219        }
220
221        Ok(())
222    }
223}
224
225/// A finalized Document ready to be rendered. We want to retain all
226/// information including spans, fields on structs, variants on enums etc.
227#[derive(Clone, Debug)]
228pub struct Document {
229    pub module_info: ModuleInfo,
230    pub item_header: ItemHeader,
231    pub item_body: ItemBody,
232    pub raw_attributes: Option<String>,
233}
234
235impl Document {
236    /// Creates an HTML file name from the [Document].
237    pub fn html_filename(&self) -> String {
238        use sway_core::language::ty::TyDecl::StorageDecl;
239        let name = match &self.item_body.ty {
240            &DocumentableType::Declared(StorageDecl { .. }) => None,
241            _ => Some(self.item_header.item_name.as_str()),
242        };
243
244        Document::create_html_filename(self.item_body.ty.doc_name(), name)
245    }
246    fn create_html_filename(ty: &str, name: Option<&str>) -> String {
247        match name {
248            Some(name) => format!("{ty}.{name}.html"),
249            None => {
250                format!("{ty}.html") // storage does not have an Ident
251            }
252        }
253    }
254    /// Generate link info used in navigation between docs.
255    pub fn link(&self) -> DocLink {
256        DocLink {
257            name: self.item_header.item_name.as_str().to_owned(),
258            module_info: self.module_info.clone(),
259            html_filename: self.html_filename(),
260            preview_opt: self.preview_opt(),
261        }
262    }
263    pub fn preview_opt(&self) -> Option<String> {
264        create_preview(self.raw_attributes.clone())
265    }
266}
267
268impl PartialEq for Document {
269    fn eq(&self, other: &Self) -> bool {
270        self.item_header.item_name == other.item_header.item_name
271            && self.item_header.module_info.module_prefixes
272                == other.item_header.module_info.module_prefixes
273    }
274}
275
276impl Deref for Documentation {
277    type Target = Vec<Document>;
278    fn deref(&self) -> &Self::Target {
279        &self.0
280    }
281}
282
283impl DerefMut for Documentation {
284    fn deref_mut(&mut self) -> &mut Self::Target {
285        &mut self.0
286    }
287}