extern crate proc_macro;
use crate::config::{HtmlAndConfig, HtmlConfig};
use crate::create::CreateHtmlMacro;
use crate::parser::HtmlParser;
use crate::tag::Tag;
use syn::parse::{Parse, ParseStream};
use syn::{parse_macro_input, parse_quote};
mod config;
mod create;
mod maybe_dollar;
mod parser;
mod tag;
#[proc_macro]
pub fn define_html_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let parsed = parse_macro_input!(input as CreateHtmlMacro);
parsed.into_tokens().into()
}
#[proc_macro]
pub fn html(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let html = parse_macro_input!(input as Html);
HtmlAndConfig {
html,
config: HtmlConfig {
real_dom_ty: parse_quote!(web_sys::Window),
},
}
.into_tokens()
.into()
}
#[proc_macro]
pub fn html_with_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let parsed = parse_macro_input!(input as HtmlAndConfig);
parsed.into_tokens().into()
}
#[proc_macro]
pub fn reflect_tokens(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let parsed = parse_macro_input!(input as proc_macro2::TokenStream);
parsed.into()
}
#[derive(Debug)]
struct Html {
tags: Vec<Tag>,
}
impl Parse for Html {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut tags = Vec::new();
while !input.is_empty() {
let tag: Tag = input.parse()?;
tags.push(tag);
}
Ok(Html { tags })
}
}
struct HtmlToTokensConfig {
real_dom_ty: syn::Type,
}
impl Html {
fn into_tokens(self, config: HtmlToTokensConfig) -> proc_macro2::TokenStream {
let html = self;
let mut html_parser = HtmlParser::new();
let parsed_tags_len = html.tags.len();
for (idx, tag) in html.tags.iter().enumerate() {
let mut next_tag = None;
if parsed_tags_len - 1 > idx {
next_tag = Some(&html.tags[idx + 1])
}
html_parser.push_tag(tag, next_tag, &config.real_dom_ty);
}
html_parser.finish()
}
}
#[cfg(test)]
pub(self) mod tests {
use super::*;
use proc_macro2::TokenStream;
use quote::quote;
#[test]
fn ui() {
let t = trybuild::TestCases::new();
let ui_tests = concat!(env!("CARGO_MANIFEST_DIR"), "/ui_tests/*.rs");
t.compile_fail(ui_tests);
}
#[test]
fn specifies_generic_param() {
let tests = [
(
quote! { <div> </div> },
quote! {
{
let mut node_0 = VirtualNode::<web_sys::Window>::new_element("div");
node_0
}
},
),
(
quote! { hello },
quote! {
{
let mut node_0 = VirtualNode::<web_sys::Window>::new_text("hello");
node_0
}
},
),
(
quote! {
{ some_variable }
},
quote! {
{
let node_0: VirtualNode::<web_sys::Window> = some_variable.into();
node_0
}
},
),
(
quote! {
<div> { some_variable } </div>
},
quote! {
{
let mut node_0 = VirtualNode::<web_sys::Window>::new_element("div");
let mut node_1: IterableNodes<web_sys::Window> = (some_variable).into();
if let Some(ref mut element_node) = node_0.as_elem_mut() {
element_node.children.extend(node_1.into_iter());
} else {
{ unreachable!("Non-elements cannot have children"); } ;
}
node_0
}
},
),
];
for (tokens, expected) in tests {
assert_expected_html_tokens(tokens, expected);
}
}
#[track_caller]
pub(super) fn assert_expected_html_tokens(start: TokenStream, expected: TokenStream) {
let html: Html = syn::parse2(start).unwrap();
let dom_ty: syn::Type = syn::parse2(quote! { web_sys::Window}).unwrap();
assert_eq!(
html.into_tokens(HtmlToTokensConfig {
real_dom_ty: dom_ty,
})
.to_string(),
expected.to_string()
);
}
}