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

feat(forge): doc #2701

Merged
merged 76 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
5c83bd8
init
rkrasiuk Aug 2, 2022
fbca151
Merge branch 'master' into rkrasiuk/feat-forge-doc
rkrasiuk Aug 2, 2022
b7fb118
stuff
rkrasiuk Aug 4, 2022
304c93d
forge doc cont
rkrasiuk Aug 10, 2022
0873dcc
fix md gen
rkrasiuk Aug 10, 2022
ca8a2b9
fix
rkrasiuk Aug 10, 2022
be28348
change doc layout & extract to builder
rkrasiuk Aug 14, 2022
3ace756
misc
rkrasiuk Aug 14, 2022
e3ea129
output format
rkrasiuk Aug 14, 2022
0b44104
Merge branch 'master' into rkrasiuk/feat-forge-doc
rkrasiuk Aug 21, 2022
42576cf
inheritance cross linking
rkrasiuk Aug 22, 2022
420804c
Merge branch 'master' into rkrasiuk/feat-forge-doc
rkrasiuk Sep 20, 2022
fe5ea03
cont
rkrasiuk Sep 21, 2022
f305879
Merge branch 'master' into rkrasiuk/feat-forge-doc
rkrasiuk Sep 21, 2022
1cbad5b
cont
rkrasiuk Sep 23, 2022
5217970
rm default level
rkrasiuk Sep 23, 2022
46b424e
book.toml & readme entry
rkrasiuk Sep 23, 2022
3b436f0
fix readme entry
rkrasiuk Sep 23, 2022
e8196f3
Merge branch 'master' of github.com:foundry-rs/foundry into rkrasiuk/…
rkrasiuk Oct 27, 2022
1fe4546
clippy
rkrasiuk Oct 27, 2022
0bc2b50
add struct doc support
rkrasiuk Oct 27, 2022
7b22362
clean up & docs
rkrasiuk Oct 27, 2022
eb4037d
remove ty from filenames
rkrasiuk Oct 28, 2022
af453e7
feat(doc): support errors, enums & render top level elements (#3565)
0xOneTony Nov 4, 2022
d9965ef
feat(doc): add out path option in config (#3643)
0xOneTony Nov 9, 2022
92d6d77
rewrite forge doc
rkrasiuk Jan 5, 2023
cba3237
Merge branch 'master' into rkrasiuk/feat-forge-doc
rkrasiuk Jan 5, 2023
ee2c80b
extract config
rkrasiuk Jan 6, 2023
750b09b
refactor parser
rkrasiuk Jan 6, 2023
3aff142
refactor format traits and add parser tests
rkrasiuk Jan 6, 2023
db88344
misc
rkrasiuk Jan 6, 2023
0f3dc36
writer & preprocessor abstractions, fix inheritance linking (aka anot…
rkrasiuk Jan 7, 2023
2b6382a
comments abstraction, add book.css, refactor and cleanup some code
rkrasiuk Jan 8, 2023
8a78776
enable contract inheritance preprocessor
rkrasiuk Jan 8, 2023
5cc9a97
display constant init value
rkrasiuk Jan 8, 2023
4d04bb3
handle files with constants
rkrasiuk Jan 8, 2023
03f5401
add missing expressions in as_code impl
rkrasiuk Jan 9, 2023
240bc7f
exit early on no sources
rkrasiuk Jan 9, 2023
43479f6
skip parentheses on shallow overrides
rkrasiuk Jan 9, 2023
6e4e6b9
add type parsing & writing
rkrasiuk Jan 9, 2023
ad701e2
basic server
rkrasiuk Jan 9, 2023
e3c4511
support overloaded functions
rkrasiuk Jan 9, 2023
2ff0693
change case
rkrasiuk Jan 9, 2023
13ba1ad
change case
rkrasiuk Jan 9, 2023
34af12b
inheritdoc preprocessor
rkrasiuk Jan 10, 2023
82f51c4
add docs to gitignore
rkrasiuk Jan 10, 2023
0470b32
rename root readme to home
rkrasiuk Jan 10, 2023
4dd1868
fallback to root readme
rkrasiuk Jan 10, 2023
9c87e02
render param name & type as code
rkrasiuk Jan 10, 2023
5be8295
format code with formatter
rkrasiuk Jan 10, 2023
b3cf4f8
trim down as_code & rename to as_string
rkrasiuk Jan 10, 2023
e722578
add link to mdbook config
rkrasiuk Jan 10, 2023
5c770d2
add prefix to dir menu entries
rkrasiuk Jan 11, 2023
33fadfd
write dev tags in italics
rkrasiuk Jan 11, 2023
9a5a0bf
support user defined book.toml
rkrasiuk Jan 11, 2023
a9ffd2b
add git source preprocessor
rkrasiuk Jan 11, 2023
2db6ba9
Merge branch 'master' into rkrasiuk/feat-forge-doc
rkrasiuk Jan 11, 2023
38b96b3
cleanup
rkrasiuk Jan 11, 2023
aac9bbc
clippy
rkrasiuk Jan 11, 2023
d560b37
add high level architecture of doc module
rkrasiuk Jan 11, 2023
5d769fa
clippy
rkrasiuk Jan 11, 2023
6cb53db
disable mdbook default features
rkrasiuk Jan 11, 2023
e5f56c2
export hostname and port as cli options
rkrasiuk Jan 11, 2023
3ec884f
fix summary path prefix stripping
rkrasiuk Jan 11, 2023
f5ac35c
Apply suggestions from code review
rkrasiuk Jan 11, 2023
db55dda
fmt
rkrasiuk Jan 11, 2023
9171ac1
fix non exhaustive structs
rkrasiuk Jan 11, 2023
5c0e91b
Merge branch 'master' into rkrasiuk/feat-forge-doc
rkrasiuk Jan 11, 2023
981a802
filter out @solidity tags
rkrasiuk Jan 11, 2023
77a1fc3
add it test and build flag
rkrasiuk Jan 11, 2023
e512047
remove serve panic hook
rkrasiuk Jan 11, 2023
7c135cd
clippy
rkrasiuk Jan 11, 2023
5474ed6
custom:name and custom:param support
rkrasiuk Jan 16, 2023
9de1b8c
specify doc.book default
rkrasiuk Jan 16, 2023
9bbd271
amend docs
rkrasiuk Jan 16, 2023
f4a5d18
Merge branch 'master' into rkrasiuk/feat-forge-doc
rkrasiuk Jan 16, 2023
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
907 changes: 542 additions & 365 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ members = [
"cli/test-utils",
"common",
"config",
"doc",
"evm",
"fmt",
"forge",
Expand Down
1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ vergen = { version = "7.0", default-features = false, features = ["build", "rust
[dependencies]
# foundry internal
forge-fmt = { path = "../fmt" }
forge-doc = { path = "../doc" }
foundry-utils = { path = "../utils" }
forge = { path = "../forge" }
foundry-config = { path = "../config" }
Expand Down
3 changes: 3 additions & 0 deletions cli/assets/.gitignoreTemplate
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ out/
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
101 changes: 101 additions & 0 deletions cli/src/cmd/forge/doc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use crate::{cmd::Cmd, opts::GH_REPO_PREFIX_REGEX};
use clap::{Parser, ValueHint};
use forge_doc::{ContractInheritance, DocBuilder, GitSource, Inheritdoc, Server};
use foundry_config::{find_project_root_path, load_config_with_root};
use std::{path::PathBuf, process::Command};

#[derive(Debug, Clone, Parser)]
pub struct DocArgs {
#[clap(
help = "The project's root path.",
long_help = "The project's root path. By default, this is the root directory of the current Git repository, or the current working directory.",
long,
value_hint = ValueHint::DirPath,
value_name = "PATH"
)]
root: Option<PathBuf>,

#[clap(
help = "The doc's output path.",
long_help = "The output path for the generated mdbook. By default, it is the `docs/` in project root.",
long = "out",
short,
value_hint = ValueHint::DirPath,
value_name = "PATH"
)]
out: Option<PathBuf>,

#[clap(help = "Build the `mdbook` from generated files.", long, short)]
build: bool,

#[clap(help = "Serve the documentation.", long, short)]
serve: bool,

#[clap(help = "Hostname for serving documentation.", long, requires = "serve")]
hostname: Option<String>,

#[clap(help = "Port for serving documentation.", long, short, requires = "serve")]
port: Option<usize>,
}

impl Cmd for DocArgs {
type Output = ();

fn run(self) -> eyre::Result<Self::Output> {
let root = self.root.clone().unwrap_or(find_project_root_path()?);
let config = load_config_with_root(Some(root.clone()));

let mut doc_config = config.doc.clone();
if let Some(out) = self.out {
doc_config.out = out;
}
if doc_config.repository.is_none() {
// Attempt to read repo from git
if let Ok(output) = Command::new("git").args(["remote", "get-url", "origin"]).output() {
if !output.stdout.is_empty() {
let remote = String::from_utf8(output.stdout)?.trim().to_owned();
if let Some(captures) = GH_REPO_PREFIX_REGEX.captures(&remote) {
let brand = captures.name("brand").unwrap().as_str();
let tld = captures.name("tld").unwrap().as_str();
let project = GH_REPO_PREFIX_REGEX.replace(&remote, "");
doc_config.repository = Some(format!(
"https://{brand}.{tld}/{}",
project.trim_end_matches(".git")
));
}
}
}
}

let commit =
Command::new("git").args(["rev-parse", "HEAD"]).output().ok().and_then(|output| {
if !output.stdout.is_empty() {
String::from_utf8(output.stdout).ok().map(|commit| commit.trim().to_owned())
} else {
None
}
});

DocBuilder::new(root.clone(), config.project_paths().sources)
.with_should_build(self.build)
.with_config(doc_config.clone())
.with_fmt(config.fmt)
.with_preprocessor(ContractInheritance::default())
.with_preprocessor(Inheritdoc::default())
.with_preprocessor(GitSource {
root,
commit,
repository: doc_config.repository.clone(),
})
.build()?;

if self.serve {
Server::new(doc_config.out)
.with_hostname(self.hostname.unwrap_or("localhost".to_owned()))
.with_port(self.port.unwrap_or(3000))
.serve()?;
}

Ok(())
}
}
1 change: 1 addition & 0 deletions cli/src/cmd/forge/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub mod config;
pub mod coverage;
pub mod create;
pub mod debug;
pub mod doc;
pub mod flatten;
pub mod fmt;
pub mod fourbyte;
Expand Down
3 changes: 3 additions & 0 deletions cli/src/forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ fn main() -> eyre::Result<()> {
Subcommands::Geiger(cmd) => {
cmd.run()?;
}
Subcommands::Doc(cmd) => {
cmd.run()?;
}
}

Ok(())
Expand Down
3 changes: 2 additions & 1 deletion cli/src/opts/dependency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use std::str::FromStr;
static GH_REPO_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new("[A-Za-z\\d-]+/[A-Za-z\\d_.-]+").unwrap());

static GH_REPO_PREFIX_REGEX: Lazy<Regex> = Lazy::new(|| {
/// Git repo prefix regex
pub static GH_REPO_PREFIX_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"((git@)|(git\+https://)|(https://)|(org-([A-Za-z0-9-])+@))?(?P<brand>[A-Za-z0-9-]+)\.(?P<tld>[A-Za-z0-9-]+)(/|:)")
.unwrap()
});
Expand Down
4 changes: 4 additions & 0 deletions cli/src/opts/forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::cmd::forge::{
config, coverage,
create::CreateArgs,
debug::DebugArgs,
doc::DocArgs,
flatten,
fmt::FmtArgs,
fourbyte::UploadSelectorsArgs,
Expand Down Expand Up @@ -149,6 +150,9 @@ pub enum Subcommands {
about = "Detects usage of unsafe cheat codes in a foundry project and its dependencies."
)]
Geiger(geiger::GeigerArgs),

#[clap(about = "Generate documentation for the project.")]
Doc(DocArgs),
}

// A set of solc compiler settings that can be set via command line arguments, which are intended
Expand Down
1 change: 1 addition & 0 deletions cli/tests/it/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ forgetest!(can_extract_config_values, |prj: TestProject, mut cmd: TestCommand| {
build_info: false,
build_info_path: None,
fmt: Default::default(),
doc: Default::default(),
fs_permissions: Default::default(),
__non_exhaustive: (),
__warnings: vec![],
Expand Down
11 changes: 11 additions & 0 deletions cli/tests/it/doc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use foundry_cli_test_utils::util::{setup_forge_remote, RemoteProject};

#[test]
fn can_generate_solmate_docs() {
let (prj, _) =
setup_forge_remote(RemoteProject::new("transmissions11/solmate").set_build(false));
prj.forge_command()
.args(["doc", "--build"])
.ensure_execute_success()
.expect("`forge doc` failed");
}
2 changes: 2 additions & 0 deletions cli/tests/it/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ mod config;
#[cfg(not(feature = "external-integration-tests"))]
mod create;
#[cfg(not(feature = "external-integration-tests"))]
mod doc;
#[cfg(not(feature = "external-integration-tests"))]
mod multi_script;
#[cfg(not(feature = "external-integration-tests"))]
mod script;
Expand Down
29 changes: 29 additions & 0 deletions config/src/doc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//! Configuration specific to the `forge doc` command and the `forge_doc` package

use serde::{Deserialize, Serialize};
use std::path::PathBuf;

/// Contains the config for parsing and rendering docs
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DocConfig {
/// Doc output path.
pub out: PathBuf,
/// The documentation title.
pub title: String,
/// Path to user provided `book.toml`.
pub book: PathBuf,
/// The repository url.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub repository: Option<String>,
}

impl Default for DocConfig {
fn default() -> Self {
Self {
out: PathBuf::from("docs"),
book: PathBuf::from("book.toml"),
title: String::default(),
repository: None,
}
}
}
8 changes: 7 additions & 1 deletion config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ pub use crate::fs_permissions::FsPermissions;
pub mod error;
pub use error::SolidityErrorCode;

pub mod doc;
pub use doc::DocConfig;

mod warning;
pub use warning::*;

Expand Down Expand Up @@ -333,6 +336,8 @@ pub struct Config {
pub build_info_path: Option<PathBuf>,
/// Configuration for `forge fmt`
pub fmt: FormatterConfig,
/// Configuration for `forge doc`
pub doc: DocConfig,
/// Configures the permissions of cheat codes that touch the file system.
///
/// This includes what operations can be executed (read, write)
Expand Down Expand Up @@ -384,7 +389,7 @@ impl Config {

/// Standalone sections in the config which get integrated into the selected profile
pub const STANDALONE_SECTIONS: &'static [&'static str] =
&["rpc_endpoints", "etherscan", "fmt", "fuzz", "invariant"];
&["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant"];

/// File name of config toml file
pub const FILE_NAME: &'static str = "foundry.toml";
Expand Down Expand Up @@ -1740,6 +1745,7 @@ impl Default for Config {
build_info: false,
build_info_path: None,
fmt: Default::default(),
doc: Default::default(),
__non_exhaustive: (),
__warnings: vec![],
}
Expand Down
47 changes: 47 additions & 0 deletions doc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[package]
name = "forge-doc"
version = "0.1.0"
edition = "2021"
description = """
Foundry's solidity doc parsing
"""
license = "MIT OR Apache-2.0"
readme = "README.md"

[dependencies]
# foundry internal
foundry-common = { path = "../common" }
forge-fmt = { path = "../fmt" }
foundry-config = { path = "../config" }

# ethers
ethers-solc = { git = "https://github.com/gakonst/ethers-rs", default-features = false, features = ["async"] }
ethers-core = { git = "https://github.com/gakonst/ethers-rs", default-features = false }

# cli
clap = { version = "3.0.10", features = [
"derive",
"env",
"unicode",
"wrap_help",
] }

# mdbook
mdbook = { version = "0.4.25", default-features = false }
warp = { version = "0.3.2", default-features = false, features = ["websocket"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
futures-util = "0.3.4"

# misc
solang-parser = "=0.1.18"
eyre = "0.6"
thiserror = "1.0.30"
rayon = "1.5.1"
itertools = "0.10.3"
toml = "0.5"
auto_impl = "1"
derive_more = "0.99"
once_cell = "1.13"

[dev-dependencies]
assert_matches = "1.5.0"
27 changes: 27 additions & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Documentation (`doc`)

Solidity documentation generator. It parses the source code and generates an mdbook
based on the parse tree and [NatSpec comments](https://docs.soliditylang.org/en/v0.8.17/natspec-format.html).

## Architecture

The entrypoint for the documentation module is the `DocBuilder`.
The `DocBuilder` generates the mdbook in 3 phases:

1. Parse

In this phase, builder invokes 2 parsers: [solang parser](https://github.com/hyperledger-labs/solang) and internal `Parser`. The solang parser produces the parse tree based on the source code. Afterwards, the internal parser walks the parse tree by implementing the `Visitor` trait from the `fmt` crate and saves important information about the parsed nodes, doc comments.

Then, builder takes the output of the internal `Parser` and creates documents with additional information: the path of the original item, display identity, the target path where this document will be written.


2. Preprocess

The builder accepts an array of preprocessors which can be applied to documents produced in the `Parse` phase. The preprocessors can rearrange and/or change the array as well as modify the separate documents.

At the end of this phase, the builder maintains a possibly modified collection of documents.


3. Write

At this point, builder has all necessary information to generate documentation for the source code. It takes every document, formats the source file contents and writes/copies additional files that are required for building documentation.
Loading