use proc_macro::TokenStream;
use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use quote::quote;
use crate::trace_tailwind::bundle_tailwind;
pub(crate) fn main_wrap(input: TokenStream) -> TokenStream {
let input2 = input.clone();
let warnings = find_warnings_in_stream(input2.clone().into(), false);
for (span, msg) in &warnings {
proc_macro_error::emit_warning!(span, msg);
}
let ast = syn::parse_macro_input!(input as syn::ItemFn);
let function_name = &ast.sig.ident;
if function_name == "vertigo_entry_function" {
emit_error!(
function_name.span(),
"Your main function cannot be named 'vertigo_entry_function' - this is reserved for WASM-JS boundary"
);
return TokenStream::new();
}
let input: proc_macro2::TokenStream = input2.into();
const VERTIGO_VERSION_MAJOR: u32 = pkg_version::pkg_version_major!();
const VERTIGO_VERSION_MINOR: u32 = pkg_version::pkg_version_minor!();
let tailwind_bundle_injector = bundle_tailwind();
quote! {
#input
#[unsafe(no_mangle)]
pub fn vertigo_entry_function(version: (u32, u32)) {
#tailwind_bundle_injector
vertigo::start_app(#function_name);
if version.0 != #VERTIGO_VERSION_MAJOR || version.1 != #VERTIGO_VERSION_MINOR {
vertigo::log::error!(
"Vertigo version mismatch, server {}.{} != client {}.{}",
version.0, version.1,
#VERTIGO_VERSION_MAJOR, #VERTIGO_VERSION_MINOR
);
}
}
}
.into()
}
fn find_warnings_in_stream(
tokens: TokenStream2,
in_dom: bool,
) -> Vec<(proc_macro2::Span, &'static str)> {
let mut iter = tokens.into_iter().peekable();
let mut warnings = Vec::new();
while let Some(tt) = iter.next() {
match tt {
TokenTree::Group(g) => {
warnings.extend(find_warnings_in_stream(g.stream(), in_dom));
}
TokenTree::Ident(id) => {
let id_str = id.to_string();
if id_str == "tw" {
if matches!(iter.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '!') {
warnings.push((id.span(), "tw! macro is not supported in #[main] body"));
} else if in_dom
&& matches!(iter.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '=')
{
warnings
.push((id.span(), "tw= attribute is not supported in #[main] body"));
}
} else if (id_str == "dom" || id_str == "dom_element" || id_str == "dom_debug")
&& matches!(iter.peek(), Some(TokenTree::Punct(p)) if p.as_char() == '!')
{
iter.next(); if let Some(TokenTree::Group(g)) = iter.peek() {
warnings.extend(find_warnings_in_stream(g.stream(), true));
iter.next(); }
}
}
_ => {}
}
}
warnings
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn test_no_warnings() {
let input = quote! {
fn main() {
let x = 1;
let y = 2;
let z = x + y;
println!("{}", z);
}
};
let warnings = find_warnings_in_stream(input, false);
assert!(warnings.is_empty());
}
#[test]
fn test_tw_macro_warning() {
let input = quote! {
fn main() {
tw!("hello");
}
};
let warnings = find_warnings_in_stream(input, false);
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].1, "tw! macro is not supported in #[main] body");
}
#[test]
fn test_tw_attribute_warning() {
let input = quote! {
fn main() {
dom_element!("div", tw="hello");
}
};
let warnings = find_warnings_in_stream(input, false);
assert_eq!(warnings.len(), 1);
assert_eq!(
warnings[0].1,
"tw= attribute is not supported in #[main] body"
);
}
#[test]
fn test_nested_dom_warning() {
let input = quote! {
fn main() {
dom! {
div {
tw!("hello");
}
}
}
};
let warnings = find_warnings_in_stream(input, false);
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].1, "tw! macro is not supported in #[main] body");
}
#[test]
fn test_dom_debug_no_warning() {
let input = quote! {
fn main() {
dom_debug!("hello");
}
};
let warnings = find_warnings_in_stream(input, false);
assert!(warnings.is_empty());
}
#[test]
fn test_complex_nested_structure() {
let input = quote! {
fn main() {
let x = 1;
dom! {
div {
tw!("hello");
dom_element!("span", tw="world");
dom_debug!("nested");
}
}
}
};
let warnings = find_warnings_in_stream(input, false);
assert_eq!(warnings.len(), 2);
assert_eq!(warnings[0].1, "tw! macro is not supported in #[main] body");
assert_eq!(
warnings[1].1,
"tw= attribute is not supported in #[main] body"
);
}
}