Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds a README to forc-doc #5126

Merged
merged 5 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
// }))
// }
eureka-cpu marked this conversation as resolved.
Show resolved Hide resolved
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
Loading