瀏覽代碼

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

Ian Macalinao 3 年之前
父節點
當前提交
de08fec0b9

+ 21 - 0
lang/syn/src/codegen/accounts/__client_accounts.rs

@@ -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),*

+ 21 - 1
lang/syn/src/codegen/accounts/__cpi_client_accounts.rs

@@ -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),*
             }

+ 1 - 0
lang/syn/src/codegen/error.rs

@@ -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),*

+ 1 - 0
lang/syn/src/codegen/program/entry.rs

@@ -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::*;
 

+ 4 - 0
lang/syn/src/lib.rs

@@ -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.

+ 17 - 0
lang/syn/src/parser/accounts/mod.rs

@@ -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 - 0
tests/docs/Anchor.toml

@@ -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 - 0
tests/docs/Cargo.toml

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

+ 19 - 0
tests/docs/package.json

@@ -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"
+  }
+}

+ 16 - 0
tests/docs/programs/docs/Cargo.toml

@@ -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" }

+ 2 - 0
tests/docs/programs/docs/Xargo.toml

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

+ 49 - 0
tests/docs/programs/docs/src/lib.rs

@@ -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,
+}