referrerpolicy=no-referrer-when-downgrade

frame_support_procedural/pallet/expand/
documentation.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//      http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use crate::pallet::Def;
19use proc_macro2::TokenStream;
20use quote::ToTokens;
21use syn::{spanned::Spanned, Attribute, Lit, LitStr};
22
23const DOC: &'static str = "doc";
24const PALLET_DOC: &'static str = "pallet_doc";
25
26/// Get the documentation file path from the `pallet_doc` attribute.
27///
28/// Supported format:
29/// `#[pallet_doc(PATH)]`: The path of the file from which the documentation is loaded
30fn parse_pallet_doc_value(attr: &Attribute) -> syn::Result<DocMetaValue> {
31	let lit: syn::LitStr = attr.parse_args().map_err(|_| {
32		let msg = "The `pallet_doc` received an unsupported argument. Supported format: `pallet_doc(\"PATH\")`";
33		syn::Error::new(attr.span(), msg)
34	})?;
35
36	Ok(DocMetaValue::Path(lit))
37}
38
39/// Get the value from the `doc` comment attribute:
40///
41/// Supported formats:
42/// - `#[doc = "A doc string"]`: Documentation as a string literal
43/// - `#[doc = include_str!(PATH)]`: Documentation obtained from a path
44fn parse_doc_value(attr: &Attribute) -> syn::Result<Option<DocMetaValue>> {
45	if !attr.path().is_ident(DOC) {
46		return Ok(None)
47	}
48
49	let meta = attr.meta.require_name_value()?;
50
51	match &meta.value {
52		syn::Expr::Lit(lit) => Ok(Some(DocMetaValue::Lit(lit.lit.clone()))),
53		syn::Expr::Macro(mac) if mac.mac.path.is_ident("include_str") =>
54			Ok(Some(DocMetaValue::Path(mac.mac.parse_body()?))),
55		_ =>
56			Err(syn::Error::new(attr.span(), "Expected `= \"docs\"` or `= include_str!(\"PATH\")`")),
57	}
58}
59
60/// Supported documentation tokens.
61#[derive(Debug)]
62enum DocMetaValue {
63	/// Documentation with string literals.
64	///
65	/// `#[doc = "Lit"]`
66	Lit(Lit),
67	/// Documentation with `include_str!` macro.
68	///
69	/// The string literal represents the file `PATH`.
70	///
71	/// `#[doc = include_str!(PATH)]`
72	Path(LitStr),
73}
74
75impl ToTokens for DocMetaValue {
76	fn to_tokens(&self, tokens: &mut TokenStream) {
77		match self {
78			DocMetaValue::Lit(lit) => lit.to_tokens(tokens),
79			DocMetaValue::Path(path_lit) => {
80				let decl = quote::quote!(include_str!(#path_lit));
81				tokens.extend(decl)
82			},
83		}
84	}
85}
86
87/// Extract the documentation from the given pallet definition
88/// to include in the runtime metadata.
89///
90/// Implement a `pallet_documentation_metadata` function to fetch the
91/// documentation that is included in the metadata.
92///
93/// The documentation is placed on the pallet similar to:
94///
95/// ```ignore
96/// #[pallet]
97/// /// Documentation for pallet
98/// #[doc = "Documentation for pallet"]
99/// #[doc = include_str!("../README.md")]
100/// #[pallet_doc("../documentation1.md")]
101/// #[pallet_doc("../documentation2.md")]
102/// pub mod pallet {}
103/// ```
104///
105/// # pallet_doc
106///
107/// The `pallet_doc` attribute can only be provided with one argument,
108/// which is the file path that holds the documentation to be added to the metadata.
109///
110/// Unlike the `doc` attribute, the documentation provided to the `proc_macro` attribute is
111/// not added to the pallet.
112pub fn expand_documentation(def: &mut Def) -> proc_macro2::TokenStream {
113	let frame_support = &def.frame_support;
114	let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site());
115	let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site());
116	let pallet_ident = &def.pallet_struct.pallet;
117	let where_clauses = &def.config.where_clause;
118
119	// TODO: Use [drain_filter](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter) when it is stable.
120
121	// The `pallet_doc` attributes are excluded from the generation of the pallet,
122	// but they are included in the runtime metadata.
123	let mut pallet_docs = Vec::with_capacity(def.item.attrs.len());
124	let mut index = 0;
125	while index < def.item.attrs.len() {
126		let attr = &def.item.attrs[index];
127		if attr.path().get_ident().map_or(false, |i| *i == PALLET_DOC) {
128			pallet_docs.push(def.item.attrs.remove(index));
129			// Do not increment the index, we have just removed the
130			// element from the attributes.
131			continue
132		}
133
134		index += 1;
135	}
136
137	// Capture the `#[doc = include_str!("../README.md")]` and `#[doc = "Documentation"]`.
138	let docs = match def
139		.item
140		.attrs
141		.iter()
142		.filter_map(|v| parse_doc_value(v).transpose())
143		.collect::<syn::Result<Vec<_>>>()
144	{
145		Ok(r) => r,
146		Err(err) => return err.into_compile_error(),
147	};
148
149	// Capture the `#[pallet_doc("../README.md")]`.
150	let pallet_docs = match pallet_docs
151		.into_iter()
152		.map(|attr| parse_pallet_doc_value(&attr))
153		.collect::<syn::Result<Vec<_>>>()
154	{
155		Ok(docs) => docs,
156		Err(err) => return err.into_compile_error(),
157	};
158
159	let docs = docs.iter().chain(pallet_docs.iter());
160
161	quote::quote!(
162		impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clauses{
163
164			#[doc(hidden)]
165			pub fn pallet_documentation_metadata()
166				-> #frame_support::__private::Vec<&'static str>
167			{
168				#frame_support::__private::vec![ #( #docs ),* ]
169			}
170		}
171	)
172}