# 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/