use crate::{
doc::{module::ModuleInfo, Document},
render::{
item::{
components::*,
context::{Context, ContextType, ItemContext},
documentable_type::DocumentableType,
},
util::format::docstring::DocStrings,
},
};
use anyhow::Result;
use sway_core::{
decl_engine::*,
language::ty::{self, TyTraitFn, TyTraitInterfaceItem},
Engines, TypeInfo,
};
use sway_features::ExperimentalFeatures;
use sway_types::{integer_bits::IntegerBits, Ident};
use swayfmt::parse;
trait RequiredMethods {
fn to_methods(&self, decl_engine: &DeclEngine) -> Vec<TyTraitFn>;
}
impl RequiredMethods for Vec<DeclRefTraitFn> {
fn to_methods(&self, decl_engine: &DeclEngine) -> Vec<TyTraitFn> {
self.iter()
.map(|decl_ref| decl_engine.get_trait_fn(decl_ref).as_ref().clone())
.collect()
}
}
#[allow(clippy::large_enum_variant)]
pub(crate) enum Descriptor {
Documentable(Document),
NonDocumentable,
}
impl Descriptor {
pub(crate) fn from_typed_decl(
decl_engine: &DeclEngine,
ty_decl: &ty::TyDecl,
module_info: ModuleInfo,
document_private_items: bool,
experimental: ExperimentalFeatures,
) -> Result<Self> {
const CONTRACT_STORAGE: &str = "Contract Storage";
match ty_decl {
ty::TyDecl::StructDecl(ty::StructDecl { decl_id, .. }) => {
let struct_decl = decl_engine.get_struct(decl_id);
if !document_private_items && struct_decl.visibility.is_private() {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = struct_decl.call_path.suffix.clone();
let attrs_opt = (!struct_decl.attributes.is_empty())
.then(|| struct_decl.attributes.to_html_string());
let context = (!struct_decl.fields.is_empty()).then_some(Context::new(
module_info.clone(),
ContextType::StructFields(struct_decl.fields.clone()),
));
Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: ty_decl.friendly_type_name(),
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemStruct>(
struct_decl.span.as_str(),
experimental,
)?,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext {
context_opt: context,
..Default::default()
},
},
raw_attributes: attrs_opt,
}))
}
}
ty::TyDecl::EnumDecl(ty::EnumDecl { decl_id, .. }) => {
let enum_decl = decl_engine.get_enum(decl_id);
if !document_private_items && enum_decl.visibility.is_private() {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = enum_decl.call_path.suffix.clone();
let attrs_opt = (!enum_decl.attributes.is_empty())
.then(|| enum_decl.attributes.to_html_string());
let context = (!enum_decl.variants.is_empty()).then_some(Context::new(
module_info.clone(),
ContextType::EnumVariants(enum_decl.variants.clone()),
));
Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: ty_decl.friendly_type_name(),
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemEnum>(
enum_decl.span.as_str(),
experimental,
)?,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext {
context_opt: context,
..Default::default()
},
},
raw_attributes: attrs_opt,
}))
}
}
ty::TyDecl::TraitDecl(ty::TraitDecl { decl_id, .. }) => {
let trait_decl = (*decl_engine.get_trait(decl_id)).clone();
if !document_private_items && trait_decl.visibility.is_private() {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = trait_decl.name;
let attrs_opt = (!trait_decl.attributes.is_empty())
.then(|| trait_decl.attributes.to_html_string());
let context =
(!trait_decl.interface_surface.is_empty()).then_some(Context::new(
module_info.clone(),
ContextType::RequiredMethods(
trait_decl
.interface_surface
.into_iter()
.filter_map(|item| match item {
TyTraitInterfaceItem::TraitFn(fn_decl) => Some(fn_decl),
_ => None,
})
.collect::<Vec<_>>()
.to_methods(decl_engine),
),
));
Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: ty_decl.friendly_type_name(),
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemTrait>(
trait_decl.span.as_str(),
experimental,
)?,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext {
context_opt: context,
..Default::default()
},
},
raw_attributes: attrs_opt,
}))
}
}
ty::TyDecl::AbiDecl(ty::AbiDecl { decl_id, .. }) => {
let abi_decl = (*decl_engine.get_abi(decl_id)).clone();
let item_name = abi_decl.name;
let attrs_opt =
(!abi_decl.attributes.is_empty()).then(|| abi_decl.attributes.to_html_string());
let context = (!abi_decl.interface_surface.is_empty()).then_some(Context::new(
module_info.clone(),
ContextType::RequiredMethods(
abi_decl
.interface_surface
.into_iter()
.flat_map(|item| match item {
TyTraitInterfaceItem::TraitFn(fn_decl) => Some(fn_decl),
_ => None,
})
.collect::<Vec<_>>()
.to_methods(decl_engine),
),
));
Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: ty_decl.friendly_type_name(),
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemAbi>(
abi_decl.span.as_str(),
experimental,
)?,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext {
context_opt: context,
..Default::default()
},
},
raw_attributes: attrs_opt,
}))
}
ty::TyDecl::StorageDecl(ty::StorageDecl { decl_id, .. }) => {
let storage_decl = decl_engine.get_storage(decl_id);
let item_name = sway_types::BaseIdent::new_no_trim(
sway_types::span::Span::from_string(CONTRACT_STORAGE.to_string()),
);
let attrs_opt = (!storage_decl.attributes.is_empty())
.then(|| storage_decl.attributes.to_html_string());
let context = (!storage_decl.fields.is_empty()).then_some(Context::new(
module_info.clone(),
ContextType::StorageFields(storage_decl.fields.clone()),
));
Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: ty_decl.friendly_type_name(),
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemStorage>(
storage_decl.span.as_str(),
experimental,
)?,
attrs_opt: attrs_opt.clone(),
item_context: ItemContext {
context_opt: context,
..Default::default()
},
},
raw_attributes: attrs_opt,
}))
}
ty::TyDecl::FunctionDecl(ty::FunctionDecl { decl_id, .. }) => {
let fn_decl = decl_engine.get_function(decl_id);
if !document_private_items && fn_decl.visibility.is_private() {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = fn_decl.name.clone();
let attrs_opt = (!fn_decl.attributes.is_empty())
.then(|| fn_decl.attributes.to_html_string());
Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: ty_decl.friendly_type_name(),
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: trim_fn_body(parse::parse_format::<sway_ast::ItemFn>(
fn_decl.span.as_str(),
experimental,
)?),
attrs_opt: attrs_opt.clone(),
item_context: ItemContext {
context_opt: None,
..Default::default()
},
},
raw_attributes: attrs_opt,
}))
}
}
ty::TyDecl::ConstantDecl(ty::ConstantDecl { decl_id, .. }) => {
let const_decl = decl_engine.get_constant(decl_id);
if !document_private_items && const_decl.visibility.is_private() {
Ok(Descriptor::NonDocumentable)
} else {
let item_name = const_decl.call_path.suffix.clone();
let attrs_opt = (!const_decl.attributes.is_empty())
.then(|| const_decl.attributes.to_html_string());
Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: ty_decl.friendly_type_name(),
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Declared(ty_decl.clone()),
item_name,
code_str: parse::parse_format::<sway_ast::ItemConst>(
const_decl.span.as_str(),
experimental,
)?,
attrs_opt: attrs_opt.clone(),
item_context: Default::default(),
},
raw_attributes: attrs_opt,
}))
}
}
_ => Ok(Descriptor::NonDocumentable),
}
}
pub(crate) fn from_type_info(
type_info: &TypeInfo,
engines: &Engines,
module_info: ModuleInfo,
) -> Result<Self> {
let item_name = Ident::new_no_span(format!("{}", engines.help_out(type_info)));
let module_info = ModuleInfo {
module_prefixes: vec![module_info.project_name().into()],
attributes: None,
};
let description = match type_info {
TypeInfo::StringSlice => "string slice",
TypeInfo::StringArray(_) => "fixed-length string",
TypeInfo::Boolean => "Boolean true or false",
TypeInfo::B256 => "256 bits (32 bytes), i.e. a hash",
TypeInfo::UnsignedInteger(bits) => match bits {
IntegerBits::Eight => "8-bit unsigned integer",
IntegerBits::Sixteen => "16-bit unsigned integer",
IntegerBits::ThirtyTwo => "32-bit unsigned integer",
IntegerBits::SixtyFour => "64-bit unsigned integer",
IntegerBits::V256 => "256-bit unsigned integer",
},
_ => return Ok(Descriptor::NonDocumentable),
};
let attrs_opt = Some(description.to_string());
match type_info {
TypeInfo::StringSlice
| TypeInfo::StringArray(_)
| TypeInfo::Boolean
| TypeInfo::B256
| TypeInfo::UnsignedInteger(_) => Ok(Descriptor::Documentable(Document {
module_info: module_info.clone(),
item_header: ItemHeader {
module_info: module_info.clone(),
friendly_name: "primitive",
item_name: item_name.clone(),
},
item_body: ItemBody {
module_info,
ty: DocumentableType::Primitive(type_info.clone()),
item_name: item_name.clone(),
code_str: item_name.to_string(),
attrs_opt: attrs_opt.clone(),
item_context: Default::default(),
},
raw_attributes: attrs_opt,
})),
_ => Ok(Descriptor::NonDocumentable),
}
}
}
fn trim_fn_body(f: String) -> String {
match f.find('{') {
Some(index) => f.split_at(index).0.to_string(),
None => f,
}
}