use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use rstml::node::{
KeyedAttributeValue, Node, NodeAttribute, NodeBlock, NodeElement, NodeFragment, NodeText,
};
use syn::{Expr, spanned::Spanned};
#[derive(Debug)]
pub struct RsxAttr {
pub key: String,
pub value: RsxAttrValue,
}
#[derive(Debug)]
pub enum RsxAttrValue {
Literal(String),
Expr(Box<Expr>),
None,
}
#[derive(Debug)]
pub enum RsxNode {
Component {
name: String,
attrs: Vec<RsxAttr>,
children: Vec<RsxNode>,
span: Span,
},
Element {
name: String,
attrs: Vec<RsxAttr>,
children: Vec<RsxNode>,
span: Span,
},
Text(String),
Block(TokenStream),
Fragment(Vec<RsxNode>),
}
pub fn translate_nodes(nodes: Vec<Node>) -> syn::Result<Vec<RsxNode>> {
nodes.into_iter().map(translate_node).collect()
}
fn translate_node(node: Node) -> syn::Result<RsxNode> {
match node {
Node::Element(el) => translate_element(el),
Node::Fragment(frag) => translate_fragment(frag),
Node::Text(text) => translate_text(text),
Node::Block(block) => translate_block(block),
Node::Comment(_) => {
Ok(RsxNode::Fragment(vec![]))
}
Node::Doctype(_) => Ok(RsxNode::Fragment(vec![])),
Node::RawText(raw) => {
let ts = raw.to_token_stream();
Ok(RsxNode::Block(ts))
}
Node::Custom(_) => unreachable!("no custom nodes configured"),
}
}
fn translate_element(el: NodeElement<rstml::node::Infallible>) -> syn::Result<RsxNode> {
let name_str = el.name().to_string();
let span = el.name().span();
let attrs = translate_attrs(el.attributes())?;
let children = translate_nodes(el.children)?;
if is_pascal_case(&name_str) {
Ok(RsxNode::Component {
name: name_str,
attrs,
children,
span,
})
} else {
Ok(RsxNode::Element {
name: name_str,
attrs,
children,
span,
})
}
}
fn translate_fragment(frag: NodeFragment<rstml::node::Infallible>) -> syn::Result<RsxNode> {
let children = translate_nodes(frag.children)?;
Ok(RsxNode::Fragment(children))
}
fn translate_text(text: NodeText) -> syn::Result<RsxNode> {
Ok(RsxNode::Text(text.value_string()))
}
fn translate_block(block: NodeBlock) -> syn::Result<RsxNode> {
match block {
NodeBlock::ValidBlock(b) => {
Ok(RsxNode::Block(b.to_token_stream()))
}
NodeBlock::Invalid(inv) => Err(syn::Error::new_spanned(
inv,
"invalid expression block in rsx!",
)),
}
}
fn translate_attrs(attrs: &[NodeAttribute]) -> syn::Result<Vec<RsxAttr>> {
let mut out = Vec::with_capacity(attrs.len());
for attr in attrs {
match attr {
NodeAttribute::Attribute(keyed) => {
let key = keyed.key.to_string();
let value = match &keyed.possible_value {
KeyedAttributeValue::Value(v) => {
if let Some(s) = v.value_literal_string() {
RsxAttrValue::Literal(s)
} else if let Some(expr) = keyed.value() {
RsxAttrValue::Expr(Box::new(expr.clone()))
} else {
RsxAttrValue::None
}
}
KeyedAttributeValue::None => RsxAttrValue::None,
KeyedAttributeValue::Binding(_) => {
return Err(syn::Error::new_spanned(
keyed,
"closure binding attributes are not supported in rsx!; use attr={expr} instead",
));
}
};
out.push(RsxAttr { key, value });
}
NodeAttribute::Block(block) => {
return Err(syn::Error::new_spanned(
block,
"dynamic attribute block is not supported in rsx!",
));
}
}
}
Ok(out)
}
pub fn is_pascal_case(name: &str) -> bool {
name.chars()
.next()
.map(|c| c.is_ascii_uppercase())
.unwrap_or(false)
}