diff --git a/forc-plugins/forc-doc/src/doc.rs b/forc-plugins/forc-doc/src/doc.rs
index 0972e8459a..910fd8a50d 100644
--- a/forc-plugins/forc-doc/src/doc.rs
+++ b/forc-plugins/forc-doc/src/doc.rs
@@ -1,6 +1,6 @@
use crate::{
descriptor::Descriptor,
- render::{split_at_markdown_header, DocLink, ItemBody, ItemHeader, Renderable},
+ render::{split_at_markdown_header, DocLink, DocStrings, ItemBody, ItemHeader, Renderable},
};
use anyhow::Result;
use horrorshow::{box_html, RenderBox};
@@ -50,35 +50,7 @@ impl Document {
}
}
fn preview_opt(&self) -> Option {
- const MAX_PREVIEW_CHARS: usize = 100;
- const CLOSING_PARAGRAPH_TAG: &str = "
";
-
- self.raw_attributes.as_ref().map(|description| {
- let preview = split_at_markdown_header(description);
- if preview.chars().count() > MAX_PREVIEW_CHARS
- && preview.contains(CLOSING_PARAGRAPH_TAG)
- {
- match preview.find(CLOSING_PARAGRAPH_TAG) {
- Some(index) => {
- // We add 1 here to get the index of the char after the closing tag.
- // This ensures we retain the closing tag and don't break the html.
- let (preview, _) =
- preview.split_at(index + CLOSING_PARAGRAPH_TAG.len() + 1);
- if preview.chars().count() > MAX_PREVIEW_CHARS && preview.contains('\n') {
- match preview.find('\n') {
- Some(index) => preview.split_at(index).0.to_string(),
- None => unreachable!("Previous logic prevents this panic"),
- }
- } else {
- preview.to_string()
- }
- }
- None => unreachable!("Previous logic prevents this panic"),
- }
- } else {
- preview.to_string()
- }
- })
+ create_preview(self.raw_attributes.clone())
}
/// Gather [Documentation] from the [TyProgram].
pub(crate) fn from_ty_program(
@@ -95,7 +67,7 @@ impl Document {
let desc = Descriptor::from_typed_decl(
decl_engine,
decl,
- ModuleInfo::from_vec(vec![project_name.to_owned()]),
+ ModuleInfo::from_ty_module(vec![project_name.to_owned()], None),
document_private_items,
)?;
@@ -108,7 +80,10 @@ impl Document {
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 = ModuleInfo::from_vec(vec![project_name.to_owned()]);
+ let attributes = (!typed_submodule.module.attributes.is_empty())
+ .then(|| typed_submodule.module.attributes.to_html_string());
+ let module_prefix =
+ ModuleInfo::from_ty_module(vec![project_name.to_owned()], attributes);
Document::from_ty_submodule(
decl_engine,
typed_submodule,
@@ -130,7 +105,7 @@ impl Document {
) -> Result<()> {
let mut new_submodule_prefix = module_prefix.to_owned();
new_submodule_prefix
- .0
+ .module_prefixes
.push(typed_submodule.library_name.as_str().to_owned());
for ast_node in &typed_submodule.module.all_nodes {
if let TyAstNodeContent::Declaration(ref decl) = ast_node.content {
@@ -173,13 +148,16 @@ impl Renderable for Document {
pub(crate) type ModulePrefix = String;
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
-pub(crate) struct ModuleInfo(pub(crate) Vec);
+pub(crate) struct ModuleInfo {
+ pub(crate) module_prefixes: Vec,
+ pub(crate) attributes: Option,
+}
impl ModuleInfo {
/// The current module.
///
/// Panics if there are no modules.
pub(crate) fn location(&self) -> &str {
- self.0
+ self.module_prefixes
.last()
.expect("Expected Some module location, found None")
}
@@ -187,7 +165,7 @@ impl ModuleInfo {
///
/// Panics if the project root is missing.
pub(crate) fn project_name(&self) -> &str {
- self.0
+ self.module_prefixes
.first()
.expect("Expected root module, project root missing")
}
@@ -197,7 +175,7 @@ impl ModuleInfo {
pub(crate) fn parent(&self) -> Option<&String> {
match self.has_parent() {
true => {
- let mut iter = self.0.iter();
+ let mut iter = self.module_prefixes.iter();
iter.next_back();
iter.next_back()
}
@@ -226,7 +204,7 @@ impl ModuleInfo {
///
/// Example: `module::submodule`
fn to_path_literal_prefix(&self, location: &str) -> String {
- let mut iter = self.0.iter();
+ let mut iter = self.module_prefixes.iter();
for prefix in iter.by_ref() {
if prefix == location {
break;
@@ -239,7 +217,7 @@ impl ModuleInfo {
///
/// This is only used for full path syntax, e.g `module/submodule/file_name.html`.
pub(crate) fn to_file_path_string(&self, file_name: &str, location: &str) -> Result {
- let mut iter = self.0.iter();
+ let mut iter = self.module_prefixes.iter();
for prefix in iter.by_ref() {
if prefix == location {
break;
@@ -265,12 +243,47 @@ impl ModuleInfo {
}
/// The depth of a module as `usize`.
pub(crate) fn depth(&self) -> usize {
- self.0.len()
+ self.module_prefixes.len()
}
/// Create a new [ModuleInfo] from a vec.
- pub(crate) fn from_vec(vec: Vec) -> Self {
- Self(vec)
+ pub(crate) fn from_ty_module(module_prefixes: Vec, attributes: Option) -> Self {
+ Self {
+ module_prefixes,
+ attributes,
+ }
}
+ pub(crate) fn preview_opt(&self) -> Option {
+ create_preview(self.attributes.clone())
+ }
+}
+
+/// Create a docstring preview from raw html attributes.
+///
+/// Returns `None` if there are no attributes.
+fn create_preview(raw_attributes: Option) -> Option {
+ const MAX_PREVIEW_CHARS: usize = 100;
+ const CLOSING_PARAGRAPH_TAG: &str = "";
+
+ raw_attributes.as_ref().map(|description| {
+ let preview = split_at_markdown_header(description);
+ if preview.chars().count() > MAX_PREVIEW_CHARS && preview.contains(CLOSING_PARAGRAPH_TAG) {
+ let closing_tag_index = preview
+ .find(CLOSING_PARAGRAPH_TAG)
+ .expect("closing tag out of range");
+ // We add 1 here to get the index of the char after the closing tag.
+ // This ensures we retain the closing tag and don't break the html.
+ let (preview, _) =
+ preview.split_at(closing_tag_index + CLOSING_PARAGRAPH_TAG.len() + 1);
+ if preview.chars().count() > MAX_PREVIEW_CHARS && preview.contains('\n') {
+ let newline_index = preview.find('\n').expect("new line char out of range");
+ preview.split_at(newline_index).0.to_string()
+ } else {
+ preview.to_string()
+ }
+ } else {
+ preview.to_string()
+ }
+ })
}
#[cfg(test)]
@@ -283,12 +296,12 @@ mod tests {
let module = String::from("module_name");
let mut module_vec = vec![project.clone(), module];
- let module_info = ModuleInfo::from_vec(module_vec.clone());
+ let module_info = ModuleInfo::from_ty_module(module_vec.clone(), None);
let project_opt = module_info.parent();
assert_eq!(Some(&project), project_opt);
module_vec.pop();
- let module_info = ModuleInfo::from_vec(module_vec);
+ let module_info = ModuleInfo::from_ty_module(module_vec, None);
let project_opt = module_info.parent();
assert_eq!(None, project_opt);
}
diff --git a/forc-plugins/forc-doc/src/main.rs b/forc-plugins/forc-doc/src/main.rs
index 63b9a90ff2..d34c0cad30 100644
--- a/forc-plugins/forc-doc/src/main.rs
+++ b/forc-plugins/forc-doc/src/main.rs
@@ -82,18 +82,22 @@ pub fn main() -> Result<()> {
no_deps,
document_private_items,
)?;
+ let root_attributes =
+ (!typed_program.root.attributes.is_empty()).then_some(typed_program.root.attributes);
+ let program_kind = typed_program.kind;
// render docs to HTML
let forc_version = pkg_manifest
.project
.forc_version
.as_ref()
.map(|ver| format!("{}.{}.{}", ver.major, ver.minor, ver.patch));
- let rendered_docs = RenderedDocumentation::from(raw_docs, forc_version)?;
+ let rendered_docs =
+ RenderedDocumentation::from(raw_docs, root_attributes, program_kind, forc_version)?;
// write contents to outfile
for doc in rendered_docs.0 {
let mut doc_path = doc_path.clone();
- for prefix in doc.module_info.0 {
+ for prefix in doc.module_info.module_prefixes {
if &prefix != project_name {
doc_path.push(prefix);
}
diff --git a/forc-plugins/forc-doc/src/render.rs b/forc-plugins/forc-doc/src/render.rs
index a779103543..a89c41f9a3 100644
--- a/forc-plugins/forc-doc/src/render.rs
+++ b/forc-plugins/forc-doc/src/render.rs
@@ -6,7 +6,7 @@ use std::collections::BTreeMap;
use std::fmt::Write;
use sway_core::language::ty::{
TyDeclaration::{self, *},
- TyEnumVariant, TyStorageField, TyStructField, TyTraitFn,
+ TyEnumVariant, TyProgramKind, TyStorageField, TyStructField, TyTraitFn,
};
use sway_core::transform::{AttributeKind, AttributesMap};
use sway_lsp::utils::markdown::format_docs;
@@ -29,14 +29,22 @@ pub(crate) struct RenderedDocumentation(pub(crate) Vec);
impl RenderedDocumentation {
/// Top level HTML rendering for all [Documentation] of a program.
- pub fn from(raw: Documentation, forc_version: Option) -> Result {
+ pub fn from(
+ raw: Documentation,
+ root_attributes: Option,
+ program_kind: TyProgramKind,
+ forc_version: Option,
+ ) -> Result {
let mut rendered_docs: RenderedDocumentation = Default::default();
let root_module = match raw.first() {
- Some(doc) => ModuleInfo::from_vec(vec![doc.module_info.project_name().to_owned()]),
+ Some(doc) => ModuleInfo::from_ty_module(
+ vec![doc.module_info.project_name().to_owned()],
+ root_attributes.map(|attrs_map| attrs_map.to_html_string()),
+ ),
None => panic!("Project does not contain a root module"),
};
let mut all_docs = DocLinks {
- style: DocStyle::AllDoc,
+ style: DocStyle::AllDoc(program_kind.as_title_str().to_string()),
links: Default::default(),
};
let mut module_map: BTreeMap>> =
@@ -138,7 +146,7 @@ impl RenderedDocumentation {
name: location.clone(),
module_info: doc.module_info.to_owned(),
html_filename: INDEX_FILENAME.to_owned(),
- preview_opt: None,
+ preview_opt: doc.module_info.preview_opt(),
};
match module_map.get_mut(parent_module) {
Some(doc_links) => match doc_links.get_mut(&BlockTitle::Modules) {
@@ -227,7 +235,7 @@ impl RenderedDocumentation {
version_opt: forc_version,
module_info: root_module.clone(),
module_docs: DocLinks {
- style: DocStyle::ProjectIndex,
+ style: DocStyle::ProjectIndex(program_kind.as_title_str().to_string()),
links: doc_links.to_owned(),
},
}
@@ -485,7 +493,7 @@ impl ItemContext {
.iter()
.map(|field| DocLink {
name: field.name.as_str().to_string(),
- module_info: ModuleInfo::from_vec(vec![]),
+ module_info: ModuleInfo::from_ty_module(vec![], None),
html_filename: format!(
"{}structfield.{}",
IDENTITY,
@@ -501,7 +509,7 @@ impl ItemContext {
.iter()
.map(|field| DocLink {
name: field.name.as_str().to_string(),
- module_info: ModuleInfo::from_vec(vec![]),
+ module_info: ModuleInfo::from_ty_module(vec![], None),
html_filename: format!(
"{}storagefield.{}",
IDENTITY,
@@ -517,7 +525,7 @@ impl ItemContext {
.iter()
.map(|variant| DocLink {
name: variant.name.as_str().to_string(),
- module_info: ModuleInfo::from_vec(vec![]),
+ module_info: ModuleInfo::from_ty_module(vec![], None),
html_filename: format!("{}variant.{}", IDENTITY, variant.name.as_str()),
preview_opt: None,
})
@@ -529,7 +537,7 @@ impl ItemContext {
.iter()
.map(|method| DocLink {
name: method.name.as_str().to_string(),
- module_info: ModuleInfo::from_vec(vec![]),
+ module_info: ModuleInfo::from_ty_module(vec![], None),
html_filename: format!(
"{}structfield.{}",
IDENTITY,
@@ -749,7 +757,7 @@ struct DocLinks {
impl Renderable for DocLinks {
fn render(self) -> Result> {
let doc_links = match self.style {
- DocStyle::AllDoc => box_html! {
+ DocStyle::AllDoc(_) => box_html! {
@ for (title, list_items) in self.links {
@ if !list_items.is_empty() {
h3(id=format!("{}", title.html_title_string())) { : title.as_str(); }
@@ -777,7 +785,7 @@ impl Renderable for DocLinks {
}
.into_string()
.unwrap(),
- DocStyle::ProjectIndex => box_html! {
+ DocStyle::ProjectIndex(_) => box_html! {
@ for (title, list_items) in self.links {
@ if !list_items.is_empty() {
h3(id=format!("{}", title.html_title_string())) { : title.as_str(); }
@@ -914,7 +922,7 @@ impl SidebarNav for AllDocIndex {
fn sidebar(&self) -> Sidebar {
Sidebar {
version_opt: None,
- style: DocStyle::AllDoc,
+ style: self.all_docs.style.clone(),
module_info: self.project_name.clone(),
href_path: INDEX_FILENAME.to_owned(),
nav: self.all_docs.clone(),
@@ -989,7 +997,7 @@ pub(crate) struct ModuleIndex {
impl SidebarNav for ModuleIndex {
fn sidebar(&self) -> Sidebar {
let style = match self.module_info.is_root_module() {
- true => DocStyle::ProjectIndex,
+ true => self.module_docs.style.clone(),
false => DocStyle::ModuleIndex,
};
Sidebar {
@@ -1006,8 +1014,8 @@ impl Renderable for ModuleIndex {
let doc_links = self.module_docs.clone().render()?;
let sidebar = self.sidebar().render()?;
let title_prefix = match self.module_docs.style {
- DocStyle::ProjectIndex => "Project ",
- DocStyle::ModuleIndex => "Module ",
+ DocStyle::ProjectIndex(ref program_type) => format!("{program_type} "),
+ DocStyle::ModuleIndex => "Module ".to_string(),
_ => unreachable!("Module Index can only be either a project or module at this time."),
};
@@ -1079,6 +1087,16 @@ impl Renderable for ModuleIndex {
}
}
}
+ @ if self.module_info.attributes.is_some() {
+ details(class="swaydoc-toggle top-doc", open) {
+ summary(class="hideme") {
+ span { : "Expand description" }
+ }
+ div(class="docblock") {
+ : Raw(self.module_info.attributes.unwrap())
+ }
+ }
+ }
: doc_links;
}
}
@@ -1094,8 +1112,8 @@ trait SidebarNav {
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
enum DocStyle {
- AllDoc,
- ProjectIndex,
+ AllDoc(String),
+ ProjectIndex(String),
ModuleIndex,
Item,
}
@@ -1115,8 +1133,8 @@ impl Renderable for Sidebar {
.module_info
.to_html_shorthand_path_string("assets/sway-logo.svg");
let location_with_prefix = match &self.style {
- DocStyle::AllDoc | DocStyle::ProjectIndex => {
- format!("Project {}", self.module_info.location())
+ DocStyle::AllDoc(project_kind) | DocStyle::ProjectIndex(project_kind) => {
+ format!("{project_kind} {}", self.module_info.location())
}
DocStyle::ModuleIndex | DocStyle::Item => format!(
"{} {}",
@@ -1125,15 +1143,17 @@ impl Renderable for Sidebar {
),
};
let (logo_path_to_parent, path_to_parent_or_self) = match &self.style {
- DocStyle::AllDoc | DocStyle::Item => (self.href_path.clone(), self.href_path.clone()),
- DocStyle::ProjectIndex => (IDENTITY.to_owned(), IDENTITY.to_owned()),
+ DocStyle::AllDoc(_) | DocStyle::Item => {
+ (self.href_path.clone(), self.href_path.clone())
+ }
+ DocStyle::ProjectIndex(_) => (IDENTITY.to_owned(), IDENTITY.to_owned()),
DocStyle::ModuleIndex => (format!("../{INDEX_FILENAME}"), IDENTITY.to_owned()),
};
// Unfortunately, match arms that return a closure, even if they are the same
// type, are incompatible. The work around is to return a String instead,
// and render it from Raw in the final output.
let styled_content = match &self.style {
- DocStyle::ProjectIndex => {
+ DocStyle::ProjectIndex(_) => {
let nav_links = self.nav.links;
let version = match self.version_opt {
Some(ref v) => v.as_str(),
diff --git a/sway-core/src/language/ty/program.rs b/sway-core/src/language/ty/program.rs
index 413d216248..d52c025182 100644
--- a/sway-core/src/language/ty/program.rs
+++ b/sway-core/src/language/ty/program.rs
@@ -465,6 +465,15 @@ impl TyProgramKind {
TyProgramKind::Script { .. } => parsed::TreeType::Script,
}
}
+ /// Used for project titles in `forc doc`.
+ pub fn as_title_str(&self) -> &str {
+ match self {
+ TyProgramKind::Contract { .. } => "Contract",
+ TyProgramKind::Library { .. } => "Library",
+ TyProgramKind::Predicate { .. } => "Predicate",
+ TyProgramKind::Script { .. } => "Script",
+ }
+ }
}
fn disallow_impure_functions(
diff --git a/sway-lib-std/src/lib.sw b/sway-lib-std/src/lib.sw
index 6b7f994d77..3e313d5368 100644
--- a/sway-lib-std/src/lib.sw
+++ b/sway-lib-std/src/lib.sw
@@ -1,3 +1,4 @@
+//! The official standard library for the Sway smart contract language.
library std;
dep error_signals;
diff --git a/sway-lsp/tests/lib.rs b/sway-lsp/tests/lib.rs
index a4a58e06d2..ede363124e 100644
--- a/sway-lsp/tests/lib.rs
+++ b/sway-lsp/tests/lib.rs
@@ -498,7 +498,7 @@ async fn go_to_definition_for_paths() {
req_uri: &uri,
req_line: 10,
req_char: 13,
- def_line: 0,
+ def_line: 1,
def_start_char: 8,
def_end_char: 11,
def_path: "sway-lib-std/src/lib.sw",
diff --git a/sway-parse/src/lib.rs b/sway-parse/src/lib.rs
index 5c09d01f70..55c98e3669 100644
--- a/sway-parse/src/lib.rs
+++ b/sway-parse/src/lib.rs
@@ -25,7 +25,11 @@ pub use crate::{
token::{lex, lex_commented},
};
-use sway_ast::{attribute::Annotated, Module, ModuleKind};
+use sway_ast::{
+ attribute::Annotated,
+ token::{DocComment, DocStyle},
+ Module, ModuleKind,
+};
use sway_error::handler::{ErrorEmitted, Handler};
use std::{path::PathBuf, sync::Arc};
@@ -45,5 +49,13 @@ pub fn parse_module_kind(
path: Option>,
) -> Result {
let ts = lex(handler, &src, 0, src.len(), path)?;
- Parser::new(handler, &ts).parse()
+ let mut parser = Parser::new(handler, &ts);
+ while let Some(DocComment {
+ doc_style: DocStyle::Inner,
+ ..
+ }) = parser.peek()
+ {
+ parser.parse::()?;
+ }
+ parser.parse()
}