lang: add more docs to macros to allow missing_doc (#1498)

This commit is contained in:
Ian Macalinao 2022-02-21 16:29:03 -06:00 committed by GitHub
parent 905d26bb09
commit de08fec0b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 166 additions and 1 deletions

View File

@ -1,6 +1,7 @@
use crate::{AccountField, AccountsStruct, Ty};
use heck::SnakeCase;
use quote::quote;
use std::str::FromStr;
// Generates the private `__client_accounts` mod implementation, containing
// a generated struct mapping 1-1 to the `Accounts` struct, except with
@ -20,6 +21,11 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
.map(|f: &AccountField| match f {
AccountField::CompositeField(s) => {
let name = &s.ident;
let docs = if !s.docs.is_empty() {
proc_macro2::TokenStream::from_str(&format!("#[doc = \"{}\"]", s.docs)).unwrap()
} else {
quote!()
};
let symbol: proc_macro2::TokenStream = format!(
"__client_accounts_{0}::{1}",
s.symbol.to_snake_case(),
@ -28,12 +34,19 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
.parse()
.unwrap();
quote! {
#docs
pub #name: #symbol
}
}
AccountField::Field(f) => {
let name = &f.ident;
let docs = if !f.docs.is_empty() {
proc_macro2::TokenStream::from_str(&format!("#[doc = \"{}\"]", f.docs)).unwrap()
} else {
quote!()
};
quote! {
#docs
pub #name: anchor_lang::solana_program::pubkey::Pubkey
}
}
@ -99,6 +112,13 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
})
.collect()
};
let struct_doc = proc_macro2::TokenStream::from_str(&format!(
"#[doc = \" Generated client accounts for [`{}`].\"]",
name
))
.unwrap();
quote! {
/// An internal, Anchor generated module. This is used (as an
/// implementation detail), to generate a struct for a given
@ -114,6 +134,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
use anchor_lang::prelude::borsh;
#(#re_exports)*
#struct_doc
#[derive(anchor_lang::AnchorSerialize)]
pub struct #name {
#(#account_struct_fields),*

View File

@ -1,3 +1,5 @@
use std::str::FromStr;
use crate::{AccountField, AccountsStruct, Ty};
use heck::SnakeCase;
use quote::quote;
@ -20,6 +22,11 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
.map(|f: &AccountField| match f {
AccountField::CompositeField(s) => {
let name = &s.ident;
let docs = if !s.docs.is_empty() {
proc_macro2::TokenStream::from_str(&format!("#[doc = \"{}\"]", s.docs)).unwrap()
} else {
quote!()
};
let symbol: proc_macro2::TokenStream = format!(
"__cpi_client_accounts_{0}::{1}",
s.symbol.to_snake_case(),
@ -28,12 +35,19 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
.parse()
.unwrap();
quote! {
#docs
pub #name: #symbol<'info>
}
}
AccountField::Field(f) => {
let name = &f.ident;
let docs = if !f.docs.is_empty() {
proc_macro2::TokenStream::from_str(&format!("#[doc = \"{}\"]", f.docs)).unwrap()
} else {
quote!()
};
quote! {
#docs
pub #name: anchor_lang::solana_program::account_info::AccountInfo<'info>
}
}
@ -124,6 +138,11 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
} else {
quote! {<'info>}
};
let struct_doc = proc_macro2::TokenStream::from_str(&format!(
"#[doc = \" Generated CPI struct of the accounts for [`{}`].\"]",
name
))
.unwrap();
quote! {
/// An internal, Anchor generated module. This is used (as an
/// implementation detail), to generate a CPI struct for a given
@ -131,12 +150,13 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
/// AccountInfo.
///
/// To access the struct in this module, one should use the sibling
/// `cpi::accounts` module (also generated), which re-exports this.
/// [`cpi::accounts`] module (also generated), which re-exports this.
pub(crate) mod #account_mod_name {
use super::*;
#(#re_exports)*
#struct_doc
pub struct #name#generics {
#(#account_struct_fields),*
}

View File

@ -61,6 +61,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
#error_enum
impl #enum_name {
/// Gets the name of this [#enum_name].
pub fn name(&self) -> String {
match self {
#(#name_variant_dispatch),*

View File

@ -73,6 +73,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
dispatch(program_id, accounts, data)
}
/// Module representing the program.
pub mod program {
use super::*;

View File

@ -207,6 +207,8 @@ pub struct Field {
pub constraints: ConstraintGroup,
pub instruction_constraints: ConstraintGroup,
pub ty: Ty,
/// Documentation string.
pub docs: String,
}
impl Field {
@ -441,6 +443,8 @@ pub struct CompositeField {
pub instruction_constraints: ConstraintGroup,
pub symbol: String,
pub raw_field: syn::Field,
/// Documentation string.
pub docs: String,
}
// A type of an account field.

View File

@ -128,6 +128,21 @@ fn constraints_cross_checks(fields: &[AccountField]) -> ParseResult<()> {
pub fn parse_account_field(f: &syn::Field, has_instruction_api: bool) -> ParseResult<AccountField> {
let ident = f.ident.clone().unwrap();
let docs: String = f
.attrs
.iter()
.map(|a| {
let meta_result = a.parse_meta();
if let Ok(syn::Meta::NameValue(meta)) = meta_result {
if meta.path.is_ident("doc") {
if let syn::Lit::Str(doc) = meta.lit {
return format!(" {}\n", doc.value().trim());
}
}
}
"".to_string()
})
.collect::<String>();
let account_field = match is_field_primitive(f)? {
true => {
let ty = parse_ty(f)?;
@ -138,6 +153,7 @@ pub fn parse_account_field(f: &syn::Field, has_instruction_api: bool) -> ParseRe
ty,
constraints: account_constraints,
instruction_constraints,
docs,
})
}
false => {
@ -149,6 +165,7 @@ pub fn parse_account_field(f: &syn::Field, has_instruction_api: bool) -> ParseRe
instruction_constraints,
symbol: ident_string(f)?,
raw_field: f.clone(),
docs,
})
}
};

11
tests/docs/Anchor.toml Normal file
View File

@ -0,0 +1,11 @@
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[programs.localnet]
errors = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[scripts]
test = "yarn run mocha -t 1000000 tests/"
[features]

4
tests/docs/Cargo.toml Normal file
View File

@ -0,0 +1,4 @@
[workspace]
members = [
"programs/*"
]

19
tests/docs/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "errors",
"version": "0.22.0",
"license": "(MIT OR Apache-2.0)",
"homepage": "https://github.com/project-serum/anchor#readme",
"bugs": {
"url": "https://github.com/project-serum/anchor/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/project-serum/anchor.git"
},
"engines": {
"node": ">=11"
},
"scripts": {
"test": "anchor test"
}
}

View File

@ -0,0 +1,16 @@
[package]
name = "docs"
version = "0.1.0"
description = "Created with Anchor"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "errors"
[features]
no-entrypoint = []
cpi = ["no-entrypoint"]
[dependencies]
anchor-lang = { path = "../../../../lang" }

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,49 @@
//! This example enforces the missing documentation lint.
#![deny(missing_docs)]
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
/// Program for testing that the `missing_docs` lint can be applied.
#[program]
mod docs {
use super::*;
/// Hello.
pub fn hello(_ctx: Context<Hello>) -> Result<()> {
err!(MyError::Hello)
}
}
/// Hello accounts.
#[derive(Accounts)]
pub struct Hello<'info> {
/// Rent sysvar.
/// Multi line docs.
pub rent: Sysvar<'info, Rent>,
/// Composite accounts test.
/// Multiple lines supported.
pub other: HelloComposite<'info>,
}
/// Hello accounts.
#[derive(Accounts)]
pub struct HelloComposite<'info> {
/// Rent sysvar 2.
pub rent2: Sysvar<'info, Rent>,
}
/// MyError.
#[error_code]
pub enum MyError {
/// test
#[msg("This is an error message clients will automatically display")]
Hello,
/// test2
HelloNoMsg = 123,
/// test3
HelloNext,
/// test4
HelloCustom,
}