Skip to content

Commit

Permalink
Implement forc doc (FuelLabs#2869)
Browse files Browse the repository at this point in the history
Opening up for visibility, still reading through `cargo doc` and
previous comments related to this

Closes FuelLabs#161 

- [x] FuelLabs#3027 
- [x] FuelLabs#3033 
- [x] FuelLabs#3036 
- [x] FuelLabs#3038 
- [x] FuelLabs#3088 
- [x] FuelLabs#3090

Co-authored-by: Alex Hansen <[email protected]>
Co-authored-by: Alex <[email protected]>
  • Loading branch information
3 people authored Oct 25, 2022
1 parent 457beff commit 203b590
Show file tree
Hide file tree
Showing 20 changed files with 928 additions and 22 deletions.
20 changes: 20 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"forc-plugins/forc-explore",
"forc-plugins/forc-fmt",
"forc-plugins/forc-lsp",
"forc-plugins/forc-doc",
"forc-tracing",
"forc-util",
"scripts/examples-checker",
Expand Down
1 change: 0 additions & 1 deletion examples/structs/Forc.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
[[package]]
name = 'structs'
source = 'root'
dependencies = []
20 changes: 20 additions & 0 deletions forc-plugins/forc-doc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[package]
name = "forc-doc"
version = "0.28.1"
authors = ["Fuel Labs <[email protected]>"]
edition = "2021"
homepage = "https://fuel.network/"
license = "Apache-2.0"
repository = "https://github.com/FuelLabs/sway"
description = "Build the documentation for the local package and all dependencies. The output is placed in `out/doc` in the same format as the project."

[dependencies]
anyhow = "1.0.65"
clap = { version = "4.0.18", features = ["derive"] }
forc-pkg = { version = "0.28.1", path = "../../forc-pkg" }
forc-util = { version = "0.28.1", path = "../../forc-util"}
horrorshow = "0.8.4"
opener = "0.5.0"
sway-core = { version = "0.28.1", path = "../../sway-core" }
sway-types = { version = "0.28.1", path = "../../sway-types" }

26 changes: 26 additions & 0 deletions forc-plugins/forc-doc/src/cli.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use clap::Parser;

#[derive(Debug, Parser)]
pub struct Command {
/// Path to the Forc.toml file. By default, Cargo searches for the Forc.toml
/// file in the current directory or any parent directory.
#[clap(long)]
pub manifest_path: Option<String>,
/// Open the docs in a browser after building them.
#[clap(long)]
pub open: bool,
/// Offline mode, prevents Forc from using the network when managing dependencies.
/// Meaning it will only try to use previously downloaded dependencies.
#[clap(long = "offline")]
pub offline: bool,
/// Silent mode. Don't output any warnings or errors to the command line.
#[clap(long = "silent", short = 's')]
pub silent: bool,
/// Requires that the Forc.lock file is up-to-date. If the lock file is missing, or it
/// needs to be updated, Forc will exit with an error
#[clap(long)]
pub locked: bool,
/// Do not build documentation for dependencies.
#[clap(long)]
pub no_deps: bool,
}
118 changes: 118 additions & 0 deletions forc-plugins/forc-doc/src/descriptor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Determine whether a [Declaration] is documentable.
use anyhow::Result;
use sway_core::{
declaration_engine::*,
language::ty::{
TyAbiDeclaration, TyConstantDeclaration, TyDeclaration, TyEnumDeclaration,
TyFunctionDeclaration, TyImplTrait, TyStorageDeclaration, TyStructDeclaration,
TyTraitDeclaration,
},
};
use sway_types::Spanned;

#[derive(Eq, PartialEq, Debug)]
// TODO: See if there's a way we can use the TyDeclarations directly
//
/// The type of [TyDeclaration] documented by the [Descriptor].
pub(crate) enum DescriptorType {
Struct(TyStructDeclaration),
Enum(TyEnumDeclaration),
Trait(TyTraitDeclaration),
Abi(TyAbiDeclaration),
Storage(TyStorageDeclaration),
ImplTraitDesc(TyImplTrait),
Function(TyFunctionDeclaration),
Const(Box<TyConstantDeclaration>),
}
impl DescriptorType {
/// Converts the [DescriptorType] to a `&str` name for HTML file name creation.
pub fn as_str(&self) -> &'static str {
use DescriptorType::*;
match self {
Struct(_) => "struct",
Enum(_) => "enum",
Trait(_) => "trait",
Abi(_) => "abi",
Storage(_) => "storage",
ImplTraitDesc(_) => "impl_trait",
Function(_) => "function",
Const(_) => "const",
}
}
}

#[derive(Eq, PartialEq)]
/// Used in deciding whether or not a [Declaration] is documentable.
pub(crate) enum Descriptor {
Documentable {
// If empty, this is the root.
module_prefix: Vec<String>,
// We want _all_ of the TyDeclaration information.
desc_ty: Box<DescriptorType>,
},
NonDocumentable,
}
impl Descriptor {
pub(crate) fn from_typed_decl(d: &TyDeclaration, module_prefix: Vec<String>) -> Result<Self> {
use TyDeclaration::*;
match d {
StructDeclaration(ref decl_id) => {
let struct_decl = de_get_struct(decl_id.clone(), &decl_id.span())?;
Ok(Descriptor::Documentable {
module_prefix,
desc_ty: Box::new(DescriptorType::Struct(struct_decl)),
})
}
EnumDeclaration(ref decl_id) => {
let enum_decl = de_get_enum(decl_id.clone(), &decl_id.span())?;
Ok(Descriptor::Documentable {
module_prefix,
desc_ty: Box::new(DescriptorType::Enum(enum_decl)),
})
}
TraitDeclaration(ref decl_id) => {
let trait_decl = de_get_trait(decl_id.clone(), &decl_id.span())?;
Ok(Descriptor::Documentable {
module_prefix,
desc_ty: Box::new(DescriptorType::Trait(trait_decl)),
})
}
AbiDeclaration(ref decl_id) => {
let abi_decl = de_get_abi(decl_id.clone(), &decl_id.span())?;
Ok(Descriptor::Documentable {
module_prefix,
desc_ty: Box::new(DescriptorType::Abi(abi_decl)),
})
}
StorageDeclaration(ref decl_id) => {
let storage_decl = de_get_storage(decl_id.clone(), &decl_id.span())?;
Ok(Descriptor::Documentable {
module_prefix,
desc_ty: Box::new(DescriptorType::Storage(storage_decl)),
})
}
ImplTrait(ref decl_id) => {
let impl_trait = de_get_impl_trait(decl_id.clone(), &decl_id.span())?;
Ok(Descriptor::Documentable {
module_prefix,
desc_ty: Box::new(DescriptorType::ImplTraitDesc(impl_trait)),
})
}
FunctionDeclaration(ref decl_id) => {
let fn_decl = de_get_function(decl_id.clone(), &decl_id.span())?;
Ok(Descriptor::Documentable {
module_prefix,
desc_ty: Box::new(DescriptorType::Function(fn_decl)),
})
}
ConstantDeclaration(ref decl_id) => {
let const_decl = de_get_constant(decl_id.clone(), &decl_id.span())?;
Ok(Descriptor::Documentable {
module_prefix,
desc_ty: Box::new(DescriptorType::Const(Box::new(const_decl))),
})
}
_ => Ok(Descriptor::NonDocumentable),
}
}
}
110 changes: 110 additions & 0 deletions forc-plugins/forc-doc/src/doc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use crate::descriptor::{Descriptor, DescriptorType};
use anyhow::Result;
use sway_core::{
language::{
ty::{TyAstNodeContent, TySubmodule},
{parsed::ParseProgram, ty::TyProgram},
},
CompileResult,
};

pub(crate) type Documentation = Vec<Document>;
/// A finalized Document ready to be rendered. We want to retain all
/// information including spans, fields on structs, variants on enums etc.
pub(crate) struct Document {
pub(crate) module_prefix: Vec<String>,
pub(crate) desc_ty: DescriptorType,
}
impl Document {
// Creates an HTML file name from the [Document].
pub fn file_name(&self) -> String {
use DescriptorType::*;
let name = match &self.desc_ty {
Struct(ty_struct_decl) => Some(ty_struct_decl.name.as_str()),
Enum(ty_enum_decl) => Some(ty_enum_decl.name.as_str()),
Trait(ty_trait_decl) => Some(ty_trait_decl.name.as_str()),
Abi(ty_abi_decl) => Some(ty_abi_decl.name.as_str()),
Storage(_) => None, // storage does not have an Ident
ImplTraitDesc(ty_impl_trait) => Some(ty_impl_trait.trait_name.suffix.as_str()), // TODO: check validity
Function(ty_fn_decl) => Some(ty_fn_decl.name.as_str()),
Const(ty_const_decl) => Some(ty_const_decl.name.as_str()),
};

Document::create_html_file_name(self.desc_ty.as_str(), name)
}
fn create_html_file_name(ty: &str, name: Option<&str>) -> String {
match name {
Some(name) => {
format!("{ty}.{name}.html")
}
None => {
format!("{ty}.html") // storage does not have an Ident
}
}
}
/// Gather [Documentation] from the [CompileResult].
pub(crate) fn from_ty_program(
compilation: &CompileResult<(ParseProgram, Option<TyProgram>)>,
no_deps: bool,
) -> Result<Documentation> {
let mut docs: Documentation = Default::default();
if let Some((_, Some(typed_program))) = &compilation.value {
for ast_node in &typed_program.root.all_nodes {
if let TyAstNodeContent::Declaration(ref decl) = ast_node.content {
let desc = Descriptor::from_typed_decl(decl, vec![])?;

if let Descriptor::Documentable {
module_prefix,
desc_ty,
} = desc
{
docs.push(Document {
module_prefix,
desc_ty: *desc_ty,
})
}
}
}

if !no_deps && !typed_program.root.submodules.is_empty() {
// this is the same process as before but for dependencies
for (_, ref typed_submodule) in &typed_program.root.submodules {
let module_prefix = vec![];
Document::from_ty_submodule(typed_submodule, &mut docs, &module_prefix)?;
}
}
}

Ok(docs)
}
fn from_ty_submodule(
typed_submodule: &TySubmodule,
docs: &mut Documentation,
module_prefix: &[String],
) -> Result<()> {
let mut new_submodule_prefix = module_prefix.to_owned();
new_submodule_prefix.push(typed_submodule.library_name.as_str().to_string());
for ast_node in &typed_submodule.module.all_nodes {
if let TyAstNodeContent::Declaration(ref decl) = ast_node.content {
let desc = Descriptor::from_typed_decl(decl, new_submodule_prefix.clone())?;

if let Descriptor::Documentable {
module_prefix,
desc_ty,
} = desc
{
docs.push(Document {
module_prefix,
desc_ty: *desc_ty,
})
}
}
}
// if there is another submodule we need to go a level deeper
if let Some((_, submodule)) = typed_submodule.module.submodules.first() {
Document::from_ty_submodule(submodule, docs, &new_submodule_prefix)?;
}

Ok(())
}
}
Loading

0 comments on commit 203b590

Please sign in to comment.