1#![doc = include_str!("../README.md")]
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{
5 parse_macro_input, spanned::Spanned, visit_mut::VisitMut, Expr, ExprCall, ExprMethodCall,
6 ExprPath, Ident, ItemMod,
7};
8
9#[proc_macro_attribute]
10pub fn console_log(attr: TokenStream, item: TokenStream) -> TokenStream {
11 let mut module = parse_macro_input!(item as ItemMod);
12 let replace_with = if attr.is_empty() {
13 quote! { println! }
14 } else {
15 attr.into()
16 };
17 let mut replacer = ConsoleLogReplacer { replace_with };
18
19 replacer.visit_item_mod_mut(&mut module);
21
22 TokenStream::from(quote! {
23 #module
24 })
25}
26
27struct ConsoleLogReplacer {
28 replace_with: proc_macro2::TokenStream,
29}
30
31impl VisitMut for ConsoleLogReplacer {
32 fn visit_expr_mut(&mut self, expr: &mut Expr) {
33 if let Expr::MethodCall(ExprMethodCall {
34 receiver,
35 method,
36 args,
37 attrs,
38 ..
39 }) = expr
40 {
41 let Expr::Path(ExprPath { path, .. }) = &**receiver else {
42 return;
43 };
44
45 let is_console = path.is_ident(&Ident::new("console", path.span()));
46 let is_log = method.to_string() == "log";
47
48 if is_console & is_log {
49 *expr = Expr::Call(ExprCall {
51 attrs: attrs.clone(),
52 func: Box::new(Expr::Verbatim(self.replace_with.clone())),
53 paren_token: Default::default(),
54 args: args.clone(),
55 });
56 }
57 }
58 syn::visit_mut::visit_expr_mut(self, expr);
59 }
60}