Skip to content

Commit

Permalink
Adds a README to forc-doc (#5126)
Browse files Browse the repository at this point in the history
Adds a `README` to forc-doc, as well as some general clean up and documentation.
  • Loading branch information
eureka-cpu authored Sep 21, 2023
1 parent ce0627e commit 503c192
Show file tree
Hide file tree
Showing 18 changed files with 361 additions and 99 deletions.
289 changes: 289 additions & 0 deletions forc-plugins/forc-doc/README.md
Original file line number Diff line number Diff line change
@@ -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<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/
1 change: 1 addition & 0 deletions forc-plugins/forc-doc/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! The command line interface for `forc doc`.
use clap::Parser;
use forc_pkg::source::IPFSNode;

Expand Down
28 changes: 0 additions & 28 deletions forc-plugins/forc-doc/src/doc/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<sway_ast::ItemImpl>(
// 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() {
Expand Down
1 change: 1 addition & 0 deletions forc-plugins/forc-doc/src/doc/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*},
Expand Down
10 changes: 10 additions & 0 deletions forc-plugins/forc-doc/src/doc/module.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -6,9 +7,18 @@ use sway_core::language::CallPath;

pub(crate) type ModulePrefixes = Vec<String>;

/// 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<String>,
}
impl ModuleInfo {
Expand Down
1 change: 0 additions & 1 deletion forc-plugins/forc-doc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Loading

0 comments on commit 503c192

Please sign in to comment.