1use std::borrow::Cow;
2
3use thiserror::Error;
4
5use crate::Config;
6
7#[derive(Clone, Debug, Eq, PartialEq)]
9pub struct DocsItem {
10 pub text: Cow<'static, str>,
12 pub span: Option<DocsSpan>,
14}
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct DocsSpan {
19 pub start: DocsLineColumn,
21 pub end: DocsLineColumn,
23}
24
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
27pub struct DocsLineColumn {
28 pub line: usize,
30 pub column: usize,
32}
33
34impl From<&syn::LitStr> for DocsItem {
35 fn from(lit_str: &syn::LitStr) -> Self {
36 let text = Cow::from(lit_str.value());
37 let span = Some(DocsSpan::from(lit_str.span()));
38 Self { text, span }
39 }
40}
41
42impl From<&'static str> for DocsItem {
43 fn from(text: &'static str) -> Self {
44 Self {
45 text: Cow::from(text),
46 span: None,
47 }
48 }
49}
50
51impl From<proc_macro2::Span> for DocsSpan {
52 fn from(span: proc_macro2::Span) -> Self {
53 let start = span.start();
54 let end = span.end();
55 Self {
56 start: DocsLineColumn {
57 line: start.line - 1,
58 column: start.column,
59 },
60 end: DocsLineColumn {
61 line: end.line - 1,
62 column: end.column,
63 },
64 }
65 }
66}
67
68pub fn build_attr_docs(
70 attr: &syn::Attribute,
71 config: &Config<'_>,
72) -> Result<impl Iterator<Item = DocsItem>, BuildAttrDocsError> {
73 Ok(build_meta_docs(&attr.meta, config)?)
74}
75
76pub fn build_meta_docs(
78 meta: &syn::Meta,
79 config: &Config<'_>,
80) -> Result<impl Iterator<Item = DocsItem>, BuildMetaDocsError> {
81 use std::vec::Vec;
82
83 if meta.path().is_ident("doc") {
84 match meta {
85 syn::Meta::NameValue(syn::MetaNameValue { value, .. }) => match value {
86 syn::Expr::Lit(syn::ExprLit {
87 lit: syn::Lit::Str(lit_str),
88 attrs,
89 }) if attrs.is_empty() => {
90 Ok(std::vec![DocsItem::from(lit_str), DocsItem::from("\n")].into_iter())
91 }
92 _ => Err(BuildMetaDocsError::NonStringDocInput(meta.clone())),
93 },
94 _ => Ok(Vec::new().into_iter()),
95 }
96 } else if meta.path().is_ident("cfg_attr") {
97 match meta {
98 syn::Meta::List(meta_list) => {
99 let mut it = meta_list
100 .parse_args::<PunctuatedMetaArgs>()
101 .map_err(|_| BuildMetaDocsError::CfgNonMetaAttribute(meta_list.clone()))?
102 .0
103 .into_iter();
104 let predicate = it
105 .next()
106 .ok_or_else(|| BuildMetaDocsError::CfgAttrWithoutPredicate(meta.clone()))?;
107 let mut it = it.peekable();
108 let _ = it
109 .peek()
110 .ok_or_else(|| BuildMetaDocsError::CfgAttrWithoutAttribute(meta.clone()))?;
111
112 let predicate_result = eval_cfg_predicate(&predicate, config)?;
113 if predicate_result {
114 let doc: Result<Vec<DocsItem>, BuildMetaDocsError> = it
115 .map(|nested_meta| build_meta_docs(&nested_meta, config))
116 .try_fold(Vec::new(), |mut acc, doc| {
117 acc.extend(doc?);
118 Ok(acc)
119 });
120 let doc = doc?;
121 Ok(doc.into_iter())
122 } else {
123 Ok(Vec::new().into_iter())
124 }
125 }
126 _ => Err(BuildMetaDocsError::NonListCfgAttrInput(meta.clone())),
127 }
128 } else {
129 Ok(Vec::new().into_iter())
130 }
131}
132
133pub fn eval_cfg_predicate(
135 meta: &syn::Meta,
136 config: &Config<'_>,
137) -> Result<bool, EvalCfgPredicateError> {
138 use std::string::ToString;
139
140 let ident = meta
141 .path()
142 .get_ident()
143 .ok_or_else(|| EvalCfgPredicateError::NonIdentPath(meta.clone()))?;
144 match meta {
145 syn::Meta::Path(_) => Ok(config.idents.contains(ident.to_string().as_str())),
146 syn::Meta::List(meta_list) => {
147 let it = meta_list
148 .parse_args::<PunctuatedMetaArgs>()
149 .map_err(|_| EvalCfgPredicateError::CfgNonMetaAttribute(meta_list.clone()))?
150 .0
151 .into_iter();
152
153 if ident == "all" {
154 for nested_meta in it {
155 match eval_cfg_predicate(&nested_meta, config) {
156 Ok(true) => {}
157 value => {
158 return value;
159 }
160 }
161 }
162 Ok(true)
163 } else if ident == "any" {
164 for nested_meta in it {
165 match eval_cfg_predicate(&nested_meta, config) {
166 Ok(false) => {}
167 value => {
168 return value;
169 }
170 }
171 }
172 Ok(false)
173 } else if ident == "not" {
174 let mut iter = it;
175 if let (Some(first), None) = (iter.next(), iter.next()) {
176 Ok(!eval_cfg_predicate(&first, config)?)
177 } else {
178 Err(EvalCfgPredicateError::NonSingleNotInput(meta.clone()))
179 }
180 } else {
181 Err(EvalCfgPredicateError::InvalidPredicateFn(meta.clone()))
182 }
183 }
184 syn::Meta::NameValue(syn::MetaNameValue { value, .. }) => match value {
185 syn::Expr::Lit(syn::ExprLit {
186 lit: syn::Lit::Str(lit_str),
187 attrs,
188 }) if attrs.is_empty() => Ok(config.name_values.contains(&(
189 Cow::from(ident.to_string().as_str()),
190 Cow::from(lit_str.value()),
191 ))),
192 _ => Err(EvalCfgPredicateError::NonStringOptionValue(meta.clone())),
193 },
194 }
195}
196
197struct PunctuatedMetaArgs(syn::punctuated::Punctuated<syn::Meta, syn::Token![,]>);
198
199impl syn::parse::Parse for PunctuatedMetaArgs {
200 fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
201 Ok(Self(syn::punctuated::Punctuated::<
202 syn::Meta,
203 syn::Token![,],
204 >::parse_terminated(input)?))
205 }
206}
207
208#[derive(Clone, Debug, Error)]
210pub enum BuildAttrDocsError {
211 #[error("Attribute parser error: {0}")]
213 SynError(#[from] syn::Error),
214 #[error("Meta parser error: {0}")]
216 MetaError(#[from] BuildMetaDocsError),
217}
218
219#[derive(Clone, Debug, Eq, Error, PartialEq)]
221pub enum BuildMetaDocsError {
222 #[error("Non-string doc attribute input: `{0:?}`.")]
224 NonStringDocInput(syn::Meta),
225 #[error("Non-list `cfg_attr` attribute input: `{0:?}`.")]
227 NonListCfgAttrInput(syn::Meta),
228 #[error("`cfg_attr` should contain predicate argument: `{0:?}`.")]
230 CfgAttrWithoutPredicate(syn::Meta),
231 #[error("`cfg_attr` should contain at least one attribute: `{0:?}`.")]
233 CfgAttrWithoutAttribute(syn::Meta),
234 #[error("`cfg_attr` attribute should be a meta attribute: `{0:?}`.")]
236 CfgNonMetaAttribute(syn::MetaList),
237 #[error(transparent)]
239 PredicateError(#[from] EvalCfgPredicateError),
240}
241
242#[derive(Clone, Debug, Eq, Error, PartialEq)]
244pub enum EvalCfgPredicateError {
245 #[error("Predicate path should be an identifier: `{0:?}`.")]
247 NonIdentPath(syn::Meta),
248 #[error("Predicate `not()` accepts only a single inner predicate: `{0:?}`.")]
250 NonSingleNotInput(syn::Meta),
251 #[error("Unknown predicate function: `{0:?}`.")]
253 InvalidPredicateFn(syn::Meta),
254 #[error("Predicatge option values can only be a string or raw string literal: `{0:?}`.")]
256 NonStringOptionValue(syn::Meta),
257 #[error("Predicate should be a meta attribute: `{0:?}`.")]
259 CfgNonMetaAttribute(syn::MetaList),
260}