diff --git a/forc-plugins/forc-doc/README.md b/forc-plugins/forc-doc/README.md new file mode 100644 index 00000000000..90531e248cd --- /dev/null +++ b/forc-plugins/forc-doc/README.md @@ -0,0 +1,289 @@ +# Forc Doc + +The Sway language documenter. + +--- + +## Quick Start + +### Prerequisites + +- Must have [`forc`][forc-reference] installed. +- Must be in a directory, or parent directory containing a [`Forc.toml`][manifest-reference] and some Sway code that successfully compiles +- For documentation to appear you need only add doc attributes to documentable items, like so: + ```sway + /// Defines my contract ABI... + abi MyContractABI {} + ``` +- You may also document at the module level with the module level doc attribute syntax: + > **Note:** This will only work at the beginning of Sway files + ```sway + //! Library containing types used for... + library; + ``` + Check out the [doc attribute section][sway-reference-attributes-doc] of the Sway reference for more information on how to document Sway code. + +If you've installed a distributed toolchain via [`fuelup`][fuelup-docs], you already have everything you need to run `forc doc`. Otherwise, you can install `forc` & `forc doc` via `cargo install`, or from `fuelup` directly. + +The below commands check you have everything necessary to run `forc doc`. + +```sh +$ cd my_fuel_project +$ ls # check Forc.toml exists +# src Forc.toml +$ forc --version # check forc is installed +$ forc doc --version # check forc doc is installed +$ forc doc --open # open docs in default browser +``` + +For usage, [see the docs][forc-doc-manual]. + +To install `forc doc` for development, see the [Getting Started](#getting-started) section under [Contributing](#contributing). + +## Contributing + +Welcome! We're glad you're here to help. Below is an overview of the program's design choices, as well as how to build `forc doc` and test your changes locally. + +### Build Requirements + +- [`cargo`][install-cargo] +- [`forc`][forc-reference] +- a default, modern browser (older browsers may cause issues) + +> **Tip:** If you see no changes take effect, it may be due to multiple `forc doc` binaries. To prevent this, remove any pre-existing versions that take precedence, such as a `fuelup` binary. You can also avoid this by executing the `forc doc` binary via `cargo run`, see [Viewing Changes](#viewing-changes). +> +> ```sh +> $ which forc-doc +> # ~/.fuelup/bin/forc-doc +> $ rm ~/.fuelup/bin/forc-doc +> $ which forc-doc +> # if it displays nothing, you're good to go! +> ``` + +### Getting Started + +Clone the `sway` repository into your preferred directory: + +```sh +$ git clone https://github.com/FuelLabs/sway.git +``` + +Then move into the newly created `sway` directory, and install `forc doc`: + +```sh +$ cd sway +$ cargo install --path forc-plugins/forc-doc +``` + +Great! Let's check everything is working as intended. Try running `forc doc` on one of the test directories: + +```sh +$ forc doc --manifest-path src/tests/data/impl_traits --open +``` + +If it succeeded, you should be seeing the test docs in your browser. + +### Development + +New language keyword? Want to add a feature? Updating CSS? `forc doc` is setup to make development easy. + +#### Design Overview + +Each section of the project is labeled to its corresponding functionality. + +- [`doc`](./src/doc/): The documenting phase. Handles analysis of a compiled typed Sway program and collects useful information into `Documents` that can be rendered to HTML. This is where to start if you are trying to implement a new Sway language feature, or make some information about an existing feature available for rendering. +- [`render`](./src/render/): Renders the information collected by the documenting phase into HTML and places them into the `out/doc` directory. This phase is intended to be especially friendly to those familiar with building static HTML webpages. The [`horrorshow` library][horrorshow] uses macros to write HTML that look strikingly similar to writing plain HTML. +- [`licenses`](./src/licenses/): Files that must be present in docs generated by `forc doc` for use of fonts, logos or anything pertaining to the project that requires a license. +- [`static.files`](./src/static.files/): Files that must be present in docs generated by `forc doc` in order for styling to take effect, eg CSS, icons & fonts. +- [`tests/data`](./src/tests/data/): This is where edge case Sway code lives. If an edge case bug arises, write a minimal reproduction and place it here to start. + +Try running `cargo doc` on the `forc-doc` project directory for an in-depth look at what each section is responsible for! + +#### The Documenting Phase + +##### Documentable Items + +Adding new documentable items is very straight-forward. Documentable items take only two forms, declarations (`TyDecl`s) and context (everything else). + +Declarations can be added directly to the description phase of the analysis, found in [`descriptor.rs`](./src/doc/descriptor.rs). Just add the new `TyDecl` to the match arm of `from_typed_decl` and fill in the necessary fields for the resulting `Descriptor` wrapped `Document`, then return it as `Documentable`. + +Context items, eg fields on structs, variants of an enum etc, must be added to the `ContextType` enum, found in [`context.rs`](./src/render/item/context.rs) and collected at the time of its corresponding `TyDecl`'s analysis. The `ContextType` is wrapped by a `Context` struct which is later sorted and rendered to the `ItemContext` of a `RenderedDocument`. + +Example: + +Let's say that we want to have a new declaration type called `CoolNewDecl`, modeled after the `StructDecl` but with some special purpose. + +First, we would add the context of the declaration to the `ContextType` as a variant: + +```rust +// in context.rs +pub(crate) enum ContextType { + // Add in the new declaration's context type + CoolNewFields(Vec), + /* ... */ +} +``` + +Then, match for the new declaration and return the `Document`. + +```rust +// in descriptor.rs +pub(crate) enum Descriptor { + Documentable(Document), + NonDocumentable, +} +impl Descriptor { + pub(crate) fn from_typed_decl(/* ... */) -> Result { + match ty_decl { + // Add the new declaration to the match arm + ty::TyDecl::CoolNewDecl(ty::CoolNewDecl { decl_id, .. }) => { + let decl = decl_engine.get_cool_new_decl(decl_id); + if !document_private_items && decl.visibility.is_private() { + Ok(Descriptor::NonDocumentable) + } else { + let item_name = decl.call_path.suffix; + let attrs_opt = (!decl.attributes.is_empty()) + .then(|| decl.attributes.to_html_string()); + // Fill in the context of the new declaration + let context = (!decl.fields.is_empty()).then_some(Context::new( + module_info.clone(), + ContextType::CoolNewFields(decl.fields), + )); + + 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_decl: ty_decl.clone(), + item_name, + code_str: swayfmt::parse::parse_format::( + decl.span.as_str(), + )?, + attrs_opt: attrs_opt.clone(), + item_context: ItemContext { + context_opt: context, + impl_traits: None, + }, + }, + raw_attributes: attrs_opt, + })) + } + } + /* ... */ + _ => Ok(Descriptor::NonDocumentable), + } + } +} +``` + +Once the declarations are collected into a `Document`, the `Document` can then be rendered. Refer to the `from_raw_docs` method on `RenderedDocumentation` found in [`render/mod.rs`](./src/render/mod.rs) for the beginning of the rendering phase. There you can find plenty of examples on how to render `Document`s into `RenderedDocument`s if you are adding in a new documentable item. + +##### Index File Generation + +Index files, such as the `AllDocIndex`, `ProjectIndex` and `ModuleIndex`s, are rendered using only the information gathered from Sway modules. The process for their rendering can also be found in the `RenderedDocumentation::from_raw_docs` method. `ModuleInfo` is gathered from at point of generating the `Documentation` from a `TyProgram`, found in [`doc/mod.rs`](./src/doc/mod.rs). This is the starting point of the entire analytical process, where a `TyProgram` is compiled and passed to `Documentation::from_ty_program`. + +#### The Rendering Phase + +As stated before, rendering is fairly straight-forward in `forc doc`, as the HTML is that of a generic webpage. + +Let's try writing a small render-side example together, using the `horrorshow` library. + +Here is the HTML for the search bar on [`docs.rs`][docs.rs]: + +```html + +``` + +Here is the corresponding `horrorshow` code that produces the same HTML: + +```rust +mod search { + use horrorshow::{box_html, RenderBox}; + + pub(crate) fn generate_searchbar() -> Box { + box_html! { + nav(class="sub") { + form(class="search-form") { + div(class="search-container") { + span; + input( + class="search-input", + name="search", + autocomplete="off", + spellcheck="false", + placeholder="Click or press ‘S’ to search, ‘?’ for more options…", + type="search" + ); + div(id="help-button", title="help", tabindex="-1") { + a(href="../help.html") { : "?" } + } + div(id="settings-menu", tabindex="-1") { + a(href="../settings.html", title="settings") { + img( + width="22", + height="22", + alt="change settings", + src="../static.files/wheel-7b819b6101059cd0.svg" + ) + } + } + } + } + } + } + } +} +``` + +Now we can call this function anytime we need to generate a searchbar for our webpage! + +### Viewing Changes + +Once you've made some changes, run the `forc doc` binary, passing it a path containing a `Forc.toml`: + +```sh +cargo run -- --manifest-path path/to/manifest --open +``` + +> **Tip:** VS Code user? Try the Live Server plugin to make viewing changes even easier. It will reload a webpage on updates, so you only need to rebuild the docs (`cargo run -- --manifest-path path/to/manifest`). Just right click the index file of docs produced by `forc doc` which can be found in the `out/doc` directory, and choose the option "open with Live Server". Voila! + +[forc-reference]: https://fuellabs.github.io/sway/master/book/forc/index.html "forc reference" +[manifest-reference]: https://fuellabs.github.io/sway/master/book/forc/manifest_reference.html "manifest reference" +[sway-reference-attributes-doc]: https://fuellabs.github.io/sway/master/book/reference/attributes.html#doc "the Sway reference - doc attribute usage" +[fuelup-docs]: https://install.fuel.network/master/ "fuelup docs" +[forc-doc-manual]: https://fuellabs.github.io/sway/master/book/forc/plugins/forc_doc.html "forc-doc manual" +[install-cargo]: https://doc.rust-lang.org/cargo/getting-started/installation.html "install cargo" +[horrorshow]: https://docs.rs/horrorshow/latest/horrorshow/ "horrorshow docs" +[docs.rs]: https://docs.rs/ diff --git a/forc-plugins/forc-doc/src/cli.rs b/forc-plugins/forc-doc/src/cli.rs index dc6e907fbab..872a65a6fe7 100644 --- a/forc-plugins/forc-doc/src/cli.rs +++ b/forc-plugins/forc-doc/src/cli.rs @@ -1,3 +1,4 @@ +//! The command line interface for `forc doc`. use clap::Parser; use forc_pkg::source::IPFSNode; diff --git a/forc-plugins/forc-doc/src/doc/descriptor.rs b/forc-plugins/forc-doc/src/doc/descriptor.rs index c11b1f4987a..2b00fc6188a 100644 --- a/forc-plugins/forc-doc/src/doc/descriptor.rs +++ b/forc-plugins/forc-doc/src/doc/descriptor.rs @@ -238,34 +238,6 @@ impl Descriptor { raw_attributes: attrs_opt, })) } - // Uncomment this when we decide how to handle ImplTraits - // ImplTrait { decl_id, decl_span, .. } => { - // TODO: figure out how to use this, likely we don't want to document this directly. - // - // This declaration type may make more sense to document as part of another declaration - // much like how we document method functions for traits or fields on structs. - // let impl_trait = decl_engine.get_impl_trait(&decl_ref, decl_span)?; - // let item_name = impl_trait.trait_name.suffix; - // Ok(Descriptor::Documentable(Document { - // module_info: module_info.clone(), - // item_header: ItemHeader { - // module_info: module_info.clone(), - // friendly_name: ty_decl.friendly_name(), - // item_name: item_name.clone(), - // }, - // item_body: ItemBody { - // module_info, - // ty_decl: ty_decl.clone(), - // item_name, - // code_str: parse::parse_format::( - // impl_trait.span.as_str(), - // ), - // attrs_opt: None, // no attributes field - // item_context: ItemContext { context: None }, - // }, - // raw_attributes: None, - // })) - // } 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() { diff --git a/forc-plugins/forc-doc/src/doc/mod.rs b/forc-plugins/forc-doc/src/doc/mod.rs index d5fe21cc475..ec235ec22a6 100644 --- a/forc-plugins/forc-doc/src/doc/mod.rs +++ b/forc-plugins/forc-doc/src/doc/mod.rs @@ -1,3 +1,4 @@ +//! Handles conversion of compiled typed Sway programs into [Document]s that can be rendered into HTML. use crate::{ doc::{descriptor::Descriptor, module::ModuleInfo}, render::{item::components::*, link::DocLink, util::format::docstring::*}, diff --git a/forc-plugins/forc-doc/src/doc/module.rs b/forc-plugins/forc-doc/src/doc/module.rs index e32585d5e11..daa34e6113f 100644 --- a/forc-plugins/forc-doc/src/doc/module.rs +++ b/forc-plugins/forc-doc/src/doc/module.rs @@ -1,3 +1,4 @@ +//! Handles the gathering of module information used in navigation and documentation of modules. use crate::render::{constant::INDEX_FILENAME, util::format::docstring::create_preview}; use anyhow::Result; use horrorshow::{box_html, Template}; @@ -6,9 +7,18 @@ use sway_core::language::CallPath; pub(crate) type ModulePrefixes = Vec; +/// Information about a Sway module. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)] pub(crate) struct ModuleInfo { + /// The preceeding module names, used in navigating between modules. pub(crate) module_prefixes: ModulePrefixes, + /// Doc attributes of a module. + /// Renders into the module level docstrings. + /// + /// ```sway + /// //! Module level docstring + /// library; + /// ``` pub(crate) attributes: Option, } impl ModuleInfo { diff --git a/forc-plugins/forc-doc/src/main.rs b/forc-plugins/forc-doc/src/main.rs index dedfd9d71fd..56b838e8a96 100644 --- a/forc-plugins/forc-doc/src/main.rs +++ b/forc-plugins/forc-doc/src/main.rs @@ -44,7 +44,6 @@ struct ProgramInfo<'a> { pkg_manifest: &'a PackageManifestFile, } -/// Main method for `forc doc`. pub fn main() -> Result<()> { let build_instructions = Command::parse(); diff --git a/forc-plugins/forc-doc/src/render/index.rs b/forc-plugins/forc-doc/src/render/index.rs index 0eb34f43d63..38fc6a39735 100644 --- a/forc-plugins/forc-doc/src/render/index.rs +++ b/forc-plugins/forc-doc/src/render/index.rs @@ -1,3 +1,4 @@ +//! Handles creation of `index.html` files. use crate::{ doc::module::ModuleInfo, render::{constant::IDENTITY, link::DocLinks, sidebar::*, BlockTitle, DocStyle, Renderable}, @@ -57,27 +58,7 @@ impl Renderable for AllDocIndex { : sidebar; main { div(class="width-limiter") { - // div(class="sub-container") { - // nav(class="sub") { - // form(class="search-form") { - // div(class="search-container") { - // span; - // input( - // class="search-input", - // name="search", - // autocomplete="off", - // spellcheck="false", - // // TODO: Add functionality. - // placeholder="Searchbar unimplemented, see issue #3480...", - // type="search" - // ); - // div(id="help-button", title="help", tabindex="-1") { - // button(type="button") { : "?" } - // } - // } - // } - // } - // } + // : generate_searchbar(); section(id="main-content", class="content") { h1(class="fqn") { span(class="in-band") { : "List of all items" } @@ -184,27 +165,7 @@ impl Renderable for ModuleIndex { : sidebar; main { div(class="width-limiter") { - // div(class="sub-container") { - // nav(class="sub") { - // form(class="search-form") { - // div(class="search-container") { - // span; - // input( - // class="search-input", - // name="search", - // autocomplete="off", - // spellcheck="false", - // // TODO: Add functionality. - // placeholder="Searchbar unimplemented, see issue #3480...", - // type="search" - // ); - // div(id="help-button", title="help", tabindex="-1") { - // button(type="button") { : "?" } - // } - // } - // } - // } - // } + // : generate_searchbar(); section(id="main-content", class="content") { div(class="main-heading") { h1(class="fqn") { diff --git a/forc-plugins/forc-doc/src/render/item/components.rs b/forc-plugins/forc-doc/src/render/item/components.rs index 1028d611c50..f4ef8afad90 100644 --- a/forc-plugins/forc-doc/src/render/item/components.rs +++ b/forc-plugins/forc-doc/src/render/item/components.rs @@ -1,3 +1,4 @@ +//! Handles creation of the head and body of an HTML doc. use crate::{ doc::module::ModuleInfo, render::{ @@ -121,27 +122,7 @@ impl Renderable for ItemBody { // this is the main code block main { div(class="width-limiter") { - // div(class="sub-container") { - // nav(class="sub") { - // form(class="search-form") { - // div(class="search-container") { - // span; - // input( - // class="search-input", - // name="search", - // autocomplete="off", - // spellcheck="false", - // // TODO: https://github.com/FuelLabs/sway/issues/3480 - // placeholder="Searchbar unimplemented, see issue #3480...", - // type="search" - // ); - // div(id="help-button", title="help", tabindex="-1") { - // button(type="button") { : "?" } - // } - // } - // } - // } - // } + // : generate_searchbar(); section(id="main-content", class="content") { div(class="main-heading") { h1(class="fqn") { diff --git a/forc-plugins/forc-doc/src/render/item/context.rs b/forc-plugins/forc-doc/src/render/item/context.rs index b16a1421b2c..7ded51ffd8d 100644 --- a/forc-plugins/forc-doc/src/render/item/context.rs +++ b/forc-plugins/forc-doc/src/render/item/context.rs @@ -1,3 +1,4 @@ +//! Manages how the context of Sway types are rendered on corresponding item pages. use crate::{ doc::module::ModuleInfo, render::{ diff --git a/forc-plugins/forc-doc/src/render/item/type_anchor.rs b/forc-plugins/forc-doc/src/render/item/type_anchor.rs index f0dfbb30940..03fd446ef8f 100644 --- a/forc-plugins/forc-doc/src/render/item/type_anchor.rs +++ b/forc-plugins/forc-doc/src/render/item/type_anchor.rs @@ -1,3 +1,4 @@ +//! Creation of HTML anchors for types that can be linked. use crate::{doc::module::ModuleInfo, RenderPlan}; use anyhow::{anyhow, Result}; use horrorshow::{box_html, RenderBox}; diff --git a/forc-plugins/forc-doc/src/render/link.rs b/forc-plugins/forc-doc/src/render/link.rs index e090a4d1709..e172e848769 100644 --- a/forc-plugins/forc-doc/src/render/link.rs +++ b/forc-plugins/forc-doc/src/render/link.rs @@ -1,3 +1,4 @@ +//! Handles creation of links for modules. use crate::{ doc::module::ModuleInfo, render::{BlockTitle, DocStyle, Renderable}, diff --git a/forc-plugins/forc-doc/src/render/mod.rs b/forc-plugins/forc-doc/src/render/mod.rs index a9ae707005f..b96d0815be6 100644 --- a/forc-plugins/forc-doc/src/render/mod.rs +++ b/forc-plugins/forc-doc/src/render/mod.rs @@ -1,3 +1,4 @@ +//! Renders [Documentation] to HTML. use crate::{ doc::{ module::{ModuleInfo, ModulePrefixes}, @@ -22,10 +23,12 @@ pub mod constant; mod index; pub mod item; pub mod link; +mod search; mod sidebar; mod title; pub mod util; +/// Something that can be rendered to HTML. pub(crate) trait Renderable { fn render(self, render_plan: RenderPlan) -> Result>; } diff --git a/forc-plugins/forc-doc/src/render/search.rs b/forc-plugins/forc-doc/src/render/search.rs new file mode 100644 index 00000000000..5cd5e8718ba --- /dev/null +++ b/forc-plugins/forc-doc/src/render/search.rs @@ -0,0 +1,39 @@ +//! Generates the searchbar. +use horrorshow::{box_html, RenderBox}; + +// TODO: Implement Searchbar +// - Add search functionality to search bar +// - Add help.html support +// - Add settings.html support +pub(crate) fn _generate_searchbar() -> Box { + box_html! { + nav(class="sub") { + form(class="search-form") { + div(class="search-container") { + span; + input( + class="search-input", + name="search", + autocomplete="off", + spellcheck="false", + placeholder="Click or press ‘S’ to search, ‘?’ for more options…", + type="search" + ); + div(id="help-button", title="help", tabindex="-1") { + a(href="../help.html") { : "?" } + } + div(id="settings-menu", tabindex="-1") { + a(href="../settings.html", title="settings") { + img( + width="22", + height="22", + alt="change settings", + src="../static.files/wheel.svg" + ) + } + } + } + } + } + } +} diff --git a/forc-plugins/forc-doc/src/render/util/format/docstring.rs b/forc-plugins/forc-doc/src/render/util/format/docstring.rs index 9d63ccf4bd3..e6c7c5f52c7 100644 --- a/forc-plugins/forc-doc/src/render/util/format/docstring.rs +++ b/forc-plugins/forc-doc/src/render/util/format/docstring.rs @@ -1,3 +1,4 @@ +//! Rendering and formatting for Sway doc attributes. use crate::render::util::format::constant::*; use comrak::{markdown_to_html, ComrakOptions}; use std::fmt::Write; diff --git a/forc-plugins/forc-doc/src/render/util/format/mod.rs b/forc-plugins/forc-doc/src/render/util/format/mod.rs index ef862622888..92bf4a8dc75 100644 --- a/forc-plugins/forc-doc/src/render/util/format/mod.rs +++ b/forc-plugins/forc-doc/src/render/util/format/mod.rs @@ -1,3 +1,4 @@ +//! Formatting utility, mainly for use in complex HTML string manipulation. pub mod code_block; mod constant; pub mod docstring; diff --git a/forc-plugins/forc-doc/src/render/util/mod.rs b/forc-plugins/forc-doc/src/render/util/mod.rs index db7b59d94c2..66f0e73acf3 100644 --- a/forc-plugins/forc-doc/src/render/util/mod.rs +++ b/forc-plugins/forc-doc/src/render/util/mod.rs @@ -1 +1,2 @@ +//! Utilities for managing edge cases in rendering types and their corresponding documentation. pub mod format; diff --git a/forc-plugins/forc-doc/src/static.files/ayu.css b/forc-plugins/forc-doc/src/static.files/ayu.css index a2883435f97..063e5b88d45 100644 --- a/forc-plugins/forc-doc/src/static.files/ayu.css +++ b/forc-plugins/forc-doc/src/static.files/ayu.css @@ -387,7 +387,7 @@ kbd { box-shadow: inset 0 -1px 0 #5c6773; } #settings-menu > a, -#help-button > button { +#help-button > a { border-color: #5c6773; background-color: #0f1419; color: #fff; @@ -412,8 +412,8 @@ kbd { } #settings-menu > a:hover, #settings-menu > a:focus, -#help-button > button:hover, -#help-button > button:focus { +#help-button > a:hover, +#help-button > a:focus { border-color: #e0e0e0; } #theme-choices { diff --git a/forc-plugins/forc-doc/src/static.files/swaydoc.css b/forc-plugins/forc-doc/src/static.files/swaydoc.css index d6c3b3c3d10..6759ed4abfc 100644 --- a/forc-plugins/forc-doc/src/static.files/swaydoc.css +++ b/forc-plugins/forc-doc/src/static.files/swaydoc.css @@ -203,7 +203,7 @@ div.impl-items > div:not(.docblock):not(.item-info), .content ul.crate a.crate, a.srclink, #main-content > .since, -#help-button > button, +#help-button > a, details.swaydoc-toggle.top-doc > summary, details.swaydoc-toggle.top-doc > summary::before, details.swaydoc-toggle.non-exhaustive > summary, @@ -1329,7 +1329,7 @@ pre.sway { height: 34px; } #settings-menu > a, -#help-button > button, +#help-button > a, #copy-path { padding: 5px; width: 33px; @@ -1341,7 +1341,7 @@ pre.sway { padding: 0; } #settings-menu > a, -#help-button > button { +#help-button > a { padding: 5px; height: 100%; display: block; @@ -1374,7 +1374,7 @@ pre.sway { input:checked + .slider { background-color: var(--settings-input-color); } -#help-button > button { +#help-button > a { text-align: center; font-size: 20px; padding-top: 2px;