# 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<TyCoolNewField>),
/* ... */
}
```
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<Self> {
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::<sway_ast::ItemCoolNew>(
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
<nav class="sub">
<form class="search-form">
<div class="search-container">
<span></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">?</a>
</div>
<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"
/>
</a>
</div>
</div>
</form>
</nav>
```
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<dyn RenderBox> {
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/