1#![doc(html_root_url = "https://docs.rs/spandoc-attribute/0.1.0")]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![warn(
6 missing_docs,
7 missing_doc_code_examples,
8 rust_2018_idioms,
9 unreachable_pub,
10 bad_style,
11 const_err,
12 dead_code,
13 improper_ctypes,
14 non_shorthand_field_patterns,
15 no_mangle_generic_items,
16 overflowing_literals,
17 path_statements,
18 patterns_in_fns_without_body,
19 private_in_public,
20 unconditional_recursion,
21 unused,
22 unused_allocation,
23 unused_comparisons,
24 unused_parens,
25 while_true
26)]
27
28use proc_macro::TokenStream;
29use proc_macro2::Ident;
30use quote::quote_spanned;
31use syn::{
32 fold::Fold, spanned::Spanned, Attribute, AttributeArgs, Block, ExprAsync, ExprAwait, ItemFn,
33 Meta, Signature,
34};
35
36#[proc_macro_attribute]
37pub fn spandoc(args: TokenStream, item: TokenStream) -> TokenStream {
39 let input: ItemFn = syn::parse_macro_input!(item as ItemFn);
40 let _args = syn::parse_macro_input!(args as AttributeArgs);
41
42 let span = input.span();
43 let ItemFn {
44 attrs,
45 vis,
46 block,
47 sig,
48 ..
49 } = input;
50
51 let Signature { ref ident, .. } = sig;
52
53 let block = SpanInstrumentedExpressions {
54 ident: ident.clone(),
55 }
56 .fold_block(*block);
57
58 quote_spanned!( span =>
59 #(#attrs) *
60 #[allow(clippy::cognitive_complexity)]
61 #vis #sig
62 #block
63 )
64 .into()
65}
66
67struct InstrumentAwaits;
68
69impl Fold for InstrumentAwaits {
70 fn fold_expr_async(&mut self, i: ExprAsync) -> ExprAsync {
71 i
72 }
73
74 fn fold_expr_await(&mut self, i: ExprAwait) -> ExprAwait {
75 let mut i = syn::fold::fold_expr_await(self, i);
76 let span = i.span();
77 let base = i.base;
78 let base = quote_spanned! { span => __fancy_guard.wrap(#base) };
79
80 let base = syn::parse2(base).unwrap();
81 i.base = Box::new(base);
82 i
83 }
84}
85
86struct SpanInstrumentedExpressions {
87 ident: Ident,
88}
89
90impl Fold for SpanInstrumentedExpressions {
91 fn fold_block(&mut self, block: Block) -> Block {
92 if block.stmts.is_empty() {
93 return block;
94 }
95
96 let block_span = block.span();
97 let mut block = syn::fold::fold_block(self, block);
98
99 let stmts = block.stmts;
100 let mut new_stmts = proc_macro2::TokenStream::new();
101 let last = stmts.len() - 1;
102
103 for (i, mut stmt) in stmts.into_iter().enumerate() {
104 let stmt_span = stmt.span();
105
106 let as_span = |attr: Attribute| {
107 let meta = attr.parse_meta().ok()?;
108 let lit = match meta {
109 Meta::NameValue(syn::MetaNameValue {
110 lit: syn::Lit::Str(lit),
111 ..
112 }) => lit,
113 _ => return None,
114 };
115
116 let (lit, args) = args::split(lit)?;
117 let span_name = format!("{}::comment", self.ident);
118
119 let span = match args {
120 Some(args) => {
121 quote_spanned! { lit.span() =>
122 tracing::span!(tracing::Level::ERROR, #span_name, #args, text = %#lit)
123 }
124 }
125 None => quote_spanned! { lit.span() =>
126 tracing::span!(tracing::Level::ERROR, #span_name, text = %#lit)
127 },
128 };
129
130 Some(span)
131 };
132
133 let attrs = if let Some(attrs) = attr::from_stmt(&mut stmt) {
134 attrs
135 } else {
136 new_stmts.extend(quote_spanned! { stmt_span => #stmt });
137 continue;
138 };
139
140 let ind = if let Some(ind) = attr::find_doc_attr_ind(attrs) {
141 ind
142 } else {
143 new_stmts.extend(quote_spanned! { stmt_span => #stmt });
144 continue;
145 };
146
147 let attr = attrs[ind].clone();
148 let span = as_span(attr);
149
150 let stmt = if span.is_some() {
151 attrs.remove(ind);
152 InstrumentAwaits.fold_stmt(stmt)
153 } else {
154 stmt
155 };
156
157 let stmts = match span {
158 Some(span) if i == last => {
159 quote_spanned! { stmt_span =>
160 let __dummy_span = #span;
161 let __fancy_guard = spandoc::FancyGuard::new(&__dummy_span);
162 #stmt
163 }
164 }
165 Some(span) => {
166 quote_spanned! { stmt_span =>
167 let __dummy_span = #span;
168 let __fancy_guard = spandoc::FancyGuard::new(&__dummy_span);
169 #stmt
170 drop(__fancy_guard);
171 drop(__dummy_span);
172 }
173 }
174 _ => quote_spanned! { stmt_span => #stmt },
175 };
176
177 new_stmts.extend(stmts);
178 }
179
180 let new_block = quote_spanned! { block_span =>
181 {
182 #new_stmts
183 }
184 };
185
186 let new_block: Block = syn::parse2(new_block).unwrap();
187
188 block.stmts = new_block.stmts;
189 block
190 }
191}
192
193mod attr {
194 use syn::{Attribute, Expr, Stmt};
195
196 pub(crate) fn from_stmt(stmt: &mut Stmt) -> Option<&mut Vec<Attribute>> {
197 match stmt {
198 syn::Stmt::Local(local) => Some(&mut local.attrs),
199 syn::Stmt::Item(_) => None,
200 syn::Stmt::Expr(expr) => from_expr(expr),
201 syn::Stmt::Semi(expr, ..) => from_expr(expr),
202 }
203 }
204
205 fn from_expr(expr: &mut Expr) -> Option<&mut Vec<Attribute>> {
206 match expr {
207 Expr::Array(e) => Some(&mut e.attrs),
208 Expr::Assign(e) => Some(&mut e.attrs),
209 Expr::AssignOp(e) => Some(&mut e.attrs),
210 Expr::Async(e) => Some(&mut e.attrs),
211 Expr::Await(e) => Some(&mut e.attrs),
212 Expr::Binary(e) => Some(&mut e.attrs),
213 Expr::Block(e) => Some(&mut e.attrs),
214 Expr::Box(e) => Some(&mut e.attrs),
215 Expr::Break(e) => Some(&mut e.attrs),
216 Expr::Call(e) => Some(&mut e.attrs),
217 Expr::Cast(e) => Some(&mut e.attrs),
218 Expr::Closure(e) => Some(&mut e.attrs),
219 Expr::Continue(e) => Some(&mut e.attrs),
220 Expr::Field(e) => Some(&mut e.attrs),
221 Expr::ForLoop(e) => Some(&mut e.attrs),
222 Expr::Group(e) => Some(&mut e.attrs),
223 Expr::If(e) => Some(&mut e.attrs),
224 Expr::Index(e) => Some(&mut e.attrs),
225 Expr::Let(e) => Some(&mut e.attrs),
226 Expr::Lit(e) => Some(&mut e.attrs),
227 Expr::Loop(e) => Some(&mut e.attrs),
228 Expr::Macro(e) => Some(&mut e.attrs),
229 Expr::Match(e) => Some(&mut e.attrs),
230 Expr::MethodCall(e) => Some(&mut e.attrs),
231 Expr::Paren(e) => Some(&mut e.attrs),
232 Expr::Path(e) => Some(&mut e.attrs),
233 Expr::Range(e) => Some(&mut e.attrs),
234 Expr::Reference(e) => Some(&mut e.attrs),
235 Expr::Repeat(e) => Some(&mut e.attrs),
236 Expr::Return(e) => Some(&mut e.attrs),
237 Expr::Struct(e) => Some(&mut e.attrs),
238 Expr::Try(e) => Some(&mut e.attrs),
239 Expr::TryBlock(e) => Some(&mut e.attrs),
240 Expr::Tuple(e) => Some(&mut e.attrs),
241 Expr::Type(e) => Some(&mut e.attrs),
242 Expr::Unary(e) => Some(&mut e.attrs),
243 Expr::Unsafe(e) => Some(&mut e.attrs),
244 Expr::Verbatim(_) => None,
245 Expr::While(e) => Some(&mut e.attrs),
246 Expr::Yield(e) => Some(&mut e.attrs),
247 _ => None,
248 }
250 }
251
252 pub(crate) fn find_doc_attr_ind(attrs: &mut Vec<Attribute>) -> Option<usize> {
253 attrs.iter().position(|attr| attr.path.is_ident("doc"))
254 }
255}
256
257mod args {
258 use core::ops::Range;
259 use syn::LitStr;
260
261 pub(crate) fn split(lit: LitStr) -> Option<(LitStr, Option<proc_macro2::TokenStream>)> {
262 let text = lit.value();
263 let text = text.trim();
264 let span = lit.span();
265
266 let text = if !text.starts_with("SPANDOC: ") {
267 return None;
268 } else {
269 text.trim_start_matches("SPANDOC: ")
270 };
271
272 if let Some((text_range, args_range)) = get_ranges(text) {
273 let args = &text[args_range];
274 let text = &text[text_range].trim();
275
276 let lit = LitStr::new(text, span);
277 let args = LitStr::new(args, span);
278 let args = args.parse().unwrap();
279
280 Some((lit, Some(args)))
281 } else {
282 let lit = LitStr::new(text, span);
283 Some((lit, None))
284 }
285 }
286
287 fn get_ranges(text: &str) -> Option<(Range<usize>, Range<usize>)> {
288 let mut depth = 0;
289
290 if !text.ends_with('}') {
291 return None;
292 }
293
294 let chars = text.chars().collect::<Vec<_>>();
295 let len = chars.len();
296
297 for (ind, c) in chars.into_iter().enumerate().rev() {
298 match c {
299 '}' => depth += 1,
300 '{' => depth -= 1,
301 _ => (),
302 }
303
304 if depth == 0 {
305 let end = len - 1;
306 return Some((0..ind, ind + 1..end));
307 }
308 }
309
310 None
311 }
312
313 #[cfg(test)]
314 pub fn split_str(text: &str) -> (&str, Option<&str>) {
315 match get_ranges(text) {
316 Some((text_range, args_range)) => {
317 let args = &text[args_range];
318 let text = &text[text_range].trim();
319
320 (text, Some(args))
321 }
322 _ => (text, None),
323 }
324 }
325}
326
327#[cfg(test)]
328mod tests {
329 use super::*;
330
331 #[test]
332 fn no_args() {
333 let input = "This doesn't have args";
334 let (text, args) = args::split_str(input);
335 assert_eq!(input, text);
336 assert_eq!(None, args);
337 }
338
339 #[test]
340 fn with_args() {
341 let input = "This doesn't have args {but, this, does}";
342 let (text, args) = args::split_str(input);
343 assert_eq!("This doesn't have args", text);
344 assert_eq!(Some("but, this, does"), args);
345 }
346}