console_log_rs/
lib.rs

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    // Print the transformed module for debugging
20    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                // Replace this with println!
50                *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}