wasm_extra_macros 0.1.1

Macros for `wasm_extra` crate.
Documentation
use proc_macro2::{Ident, TokenStream};
use syn::{parse::{Parse, ParseStream, ParseBuffer}, token::{Comma, And}, LitStr, braced, ExprClosure};
use quote::{quote, ToTokens};

pub(crate) enum EventTarget {
    SinglyReferred(Ident),
    DoublyReferred(Ident),
}

impl EventTarget {
    pub(crate) fn target_obj_ident(&self) -> EventTargetObjIdent {
        match self {
            Self::SinglyReferred(ident) => EventTargetObjIdent::SinglyReferred(ident),
            Self::DoublyReferred(_) => EventTargetObjIdent::DoublyReferred,
        }
    }
    pub(crate) fn preprologue(&self) -> Option<ClosurePreprologue> {
        match self {
            Self::SinglyReferred(_) => None,
            Self::DoublyReferred(ident) => Some(ClosurePreprologue { doubly_referred_target: ident }),
        }
    }
}

pub(crate) struct ClosurePreprologue<'a> {
    pub(crate) doubly_referred_target: &'a Ident,
}


impl<'a> ToTokens for ClosurePreprologue<'a> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let Self { doubly_referred_target } = self;
        tokens.extend(quote! {
            let __event_target_alias = #doubly_referred_target.as_ref();
        });
    }
}

pub(crate) enum EventTargetObjIdent<'a> {
    SinglyReferred(&'a Ident),
    DoublyReferred,
}

impl<'a> ToTokens for EventTargetObjIdent<'a> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::SinglyReferred(ident) => tokens.extend(quote! { #ident }),
            Self::DoublyReferred => tokens.extend(quote! { __event_target_alias }),
        }
    }
}

pub(crate) struct Args {
    pub(crate) target: EventTarget,
    pub(crate) event: LitStr,
    pub(crate) closure_prologue: TokenStream,
    pub(crate) closure: ExprClosure,
}

#[derive(derive_syn_parse::Parse)]
pub(crate) struct FnMutArgs(pub(crate) Args);

#[derive(derive_syn_parse::Parse)]
pub(crate) struct FnOnceArgs(pub(crate) Args);

impl Parse for EventTarget {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        if input.peek(And) {
            input.parse::<And>()?;
            let ident = input.parse::<Ident>()?;
            Ok(Self::DoublyReferred(ident))
        } else {
            let ident = input.parse::<Ident>()?;
            Ok(Self::SinglyReferred(ident))
        }
    }
}

impl Parse for Args {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let target = input.parse::<EventTarget>()?;
        input.parse::<Comma>()?;
        let event = input.parse::<LitStr>()?;
        input.parse::<Comma>()?;
        let closure_prologue = {
            let contents: ParseBuffer;
            braced!(contents in input);
            contents.parse::<TokenStream>()
        }?;
        input.parse::<Comma>()?;
        let closure = input.parse::<ExprClosure>()?;
        
        Ok(Self { target, event, closure_prologue, closure })
    }
}

impl FnMutArgs {
    pub(crate) fn handle(self) -> TokenStream {
        let Args { target, event, closure_prologue, closure } = self.0;
        let target_obj_ident = target.target_obj_ident();
        let preprologue: Option<ClosurePreprologue> = target.preprologue();
        quote! {
            {
                #preprologue
                #closure_prologue
                let __handler = ::wasm_bindgen::closure::Closure::<dyn ::core::ops::FnMut(_)>::new::<_>(
                    #closure
                );
                #target_obj_ident.add_event_listener_with_callback(
                    #event,
                    ::wasm_bindgen::JsCast::unchecked_ref(__handler.as_ref())
                ).unwrap();
                ::wasm_bindgen::closure::Closure::forget(__handler);
            }
        }
    }
}

impl FnOnceArgs {
    pub(crate) fn handle(self) -> TokenStream {
        let Args { target, event, closure_prologue, closure } = self.0;
        let target_obj_ident = target.target_obj_ident();
        let preprologue: Option<ClosurePreprologue> = target.preprologue();
        quote! {
            {
                #preprologue
                #closure_prologue
                let __handler = ::wasm_bindgen::closure::Closure::once(
                    #closure
                );
                #target_obj_ident.add_event_listener_with_callback(
                    #event,
                    ::wasm_bindgen::JsCast::unchecked_ref(__handler.as_ref())
                ).unwrap();
                ::wasm_bindgen::closure::Closure::forget(__handler);
            }
        }
    }
}