rstml 0.12 and enforce braces in attribute values

This commit is contained in:
blorbb 2024-08-01 22:02:27 +10:00 committed by Greg Johnston
parent 58476bb98e
commit 6a4fc96835
7 changed files with 63 additions and 35 deletions

View file

@ -20,7 +20,7 @@ syn = { version = "2.0", features = [
"printing", "printing",
] } ] }
quote = "1.0" quote = "1.0"
rstml = "0.11.2" rstml = "0.12.0"
proc-macro2 = { version = "1.0", features = ["span-locations", "nightly"] } proc-macro2 = { version = "1.0", features = ["span-locations", "nightly"] }
parking_lot = "0.12.3" parking_lot = "0.12.3"
walkdir = "2.5" walkdir = "2.5"

View file

@ -1,4 +1,4 @@
use rstml::node::{NodeElement, NodeName}; use rstml::node::{CustomNode, NodeElement, NodeName};
/// Converts `syn::Block` to simple expression /// Converts `syn::Block` to simple expression
/// ///
@ -65,6 +65,6 @@ pub fn is_component_tag_name(name: &NodeName) -> bool {
} }
#[must_use] #[must_use]
pub fn is_component_node(node: &NodeElement) -> bool { pub fn is_component_node(node: &NodeElement<impl CustomNode>) -> bool {
is_component_tag_name(node.name()) is_component_tag_name(node.name())
} }

View file

@ -22,7 +22,7 @@ proc-macro-error = { version = "1.0", default-features = false }
proc-macro2 = "1.0" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
syn = { version = "2.0", features = ["full"] } syn = { version = "2.0", features = ["full"] }
rstml = "0.11.2" rstml = "0.12.0"
leptos_hot_reload = { workspace = true } leptos_hot_reload = { workspace = true }
server_fn_macro = { workspace = true } server_fn_macro = { workspace = true }
convert_case = "0.6.0" convert_case = "0.6.0"

View file

@ -306,12 +306,19 @@ pub fn view(tokens: TokenStream) -> TokenStream {
global_class.as_ref(), global_class.as_ref(),
normalized_call_site(proc_macro::Span::call_site()), normalized_call_site(proc_macro::Span::call_site()),
); );
// The allow lint needs to be put here instead of at the expansion of
// view::attribute_value(). Adding this next to the expanded expression
// seems to break rust-analyzer, but it works when the allow is put here.
quote! { quote! {
{
#[allow(unused_braces)]
{ {
#(#errors;)* #(#errors;)*
#nodes_output #nodes_output
} }
} }
}
.into() .into()
} }

View file

@ -3,13 +3,13 @@ use crate::view::attribute_absolute;
use proc_macro2::{Ident, TokenStream, TokenTree}; use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned}; use quote::{format_ident, quote, quote_spanned};
use rstml::node::{ use rstml::node::{
KeyedAttributeValue, NodeAttribute, NodeBlock, NodeElement, NodeName, CustomNode, KeyedAttributeValue, NodeAttribute, NodeBlock, NodeElement, NodeName
}; };
use std::collections::HashMap; use std::collections::HashMap;
use syn::{spanned::Spanned, Expr, ExprPath, ExprRange, RangeLimits, Stmt}; use syn::{spanned::Spanned, Expr, ExprPath, ExprRange, RangeLimits, Stmt};
pub(crate) fn component_to_tokens( pub(crate) fn component_to_tokens(
node: &NodeElement, node: &NodeElement<impl CustomNode>,
global_class: Option<&TokenTree>, global_class: Option<&TokenTree>,
) -> TokenStream { ) -> TokenStream {
let name = node.name(); let name = node.name();
@ -151,7 +151,7 @@ pub(crate) fn component_to_tokens(
} }
})) }))
} else if let NodeAttribute::Attribute(node) = attr { } else if let NodeAttribute::Attribute(node) = attr {
attribute_absolute(node, idx >= spread_marker) attribute_absolute(&node, idx >= spread_marker)
} else { } else {
None None
} }

View file

@ -10,8 +10,8 @@ use proc_macro2::{Ident, Span, TokenStream, TokenTree};
use proc_macro_error::abort; use proc_macro_error::abort;
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use rstml::node::{ use rstml::node::{
KeyedAttribute, Node, NodeAttribute, NodeBlock, NodeElement, NodeName, CustomNode, KVAttributeValue, KeyedAttribute, Node, NodeAttribute,
NodeNameFragment, NodeBlock, NodeElement, NodeName, NodeNameFragment,
}; };
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use syn::{ use syn::{
@ -89,7 +89,7 @@ pub fn render_view(
} }
fn element_children_to_tokens( fn element_children_to_tokens(
nodes: &[Node], nodes: &[Node<impl CustomNode>],
parent_type: TagType, parent_type: TagType,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>, parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>, global_class: Option<&TokenTree>,
@ -117,7 +117,7 @@ fn element_children_to_tokens(
} }
fn fragment_to_tokens( fn fragment_to_tokens(
nodes: &[Node], nodes: &[Node<impl CustomNode>],
parent_type: TagType, parent_type: TagType,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>, parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>, global_class: Option<&TokenTree>,
@ -142,7 +142,7 @@ fn fragment_to_tokens(
} }
fn children_to_tokens( fn children_to_tokens(
nodes: &[Node], nodes: &[Node<impl CustomNode>],
parent_type: TagType, parent_type: TagType,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>, parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>, global_class: Option<&TokenTree>,
@ -186,7 +186,7 @@ fn children_to_tokens(
} }
fn node_to_tokens( fn node_to_tokens(
node: &Node, node: &Node<impl CustomNode>,
parent_type: TagType, parent_type: TagType,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>, parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>, global_class: Option<&TokenTree>,
@ -219,6 +219,7 @@ fn node_to_tokens(
global_class, global_class,
view_marker, view_marker,
), ),
Node::Custom(node) => Some(node.to_token_stream()),
} }
} }
@ -236,7 +237,7 @@ fn text_to_tokens(text: &LitStr) -> TokenStream {
} }
pub(crate) fn element_to_tokens( pub(crate) fn element_to_tokens(
node: &NodeElement, node: &NodeElement<impl CustomNode>,
mut parent_type: TagType, mut parent_type: TagType,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>, parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>, global_class: Option<&TokenTree>,
@ -298,7 +299,7 @@ pub(crate) fn element_to_tokens(
} }
} }
NodeAttribute::Attribute(node) => { NodeAttribute::Attribute(node) => {
if let Some(content) = attribute_absolute(node, true) { if let Some(content) = attribute_absolute(&node, true) {
attributes.push(content); attributes.push(content);
} }
} }
@ -411,7 +412,7 @@ pub(crate) fn element_to_tokens(
} }
} }
fn is_spread_marker(node: &NodeElement) -> bool { fn is_spread_marker(node: &NodeElement<impl CustomNode>) -> bool {
match node.name() { match node.name() {
NodeName::Block(block) => matches!( NodeName::Block(block) => matches!(
block.stmts.first(), block.stmts.first(),
@ -763,7 +764,7 @@ fn is_custom_element(tag: &str) -> bool {
tag.contains('-') tag.contains('-')
} }
fn is_self_closing(node: &NodeElement) -> bool { fn is_self_closing(node: &NodeElement<impl CustomNode>) -> bool {
// self-closing tags // self-closing tags
// https://developer.mozilla.org/en-US/docs/Glossary/Empty_element // https://developer.mozilla.org/en-US/docs/Glossary/Empty_element
[ [
@ -911,9 +912,20 @@ fn attribute_name(name: &NodeName) -> TokenStream {
} }
fn attribute_value(attr: &KeyedAttribute) -> TokenStream { fn attribute_value(attr: &KeyedAttribute) -> TokenStream {
match attr.value() { match attr.possible_value.to_value() {
Some(value) => { None => quote! { true },
if let Expr::Lit(lit) = value { Some(value) => match &value.value {
KVAttributeValue::Expr(expr) => {
// value must be a block or literal
if !matches!(expr, Expr::Block(_) | Expr::Lit(_)) {
emit_error!(
expr.span(),
"attribute values must be surrounded by braces or be literals";
help = "wrap the expression in braces: {{{}}}", expr.to_token_stream(),
)
}
if let Expr::Lit(lit) = expr {
if cfg!(feature = "nightly") { if cfg!(feature = "nightly") {
if let Lit::Str(str) = &lit.lit { if let Lit::Str(str) = &lit.lit {
return quote! { return quote! {
@ -922,9 +934,18 @@ fn attribute_value(attr: &KeyedAttribute) -> TokenStream {
} }
} }
} }
quote! { #value }
quote! {
{#expr}
} }
None => quote! { true }, }
// any value in braces: expand as-is to give proper r-a support
KVAttributeValue::InvalidBraced(block) => {
quote! {
#block
}
}
},
} }
} }

View file

@ -2,12 +2,12 @@ use super::{convert_to_snake_case, ident_from_tag_name};
use crate::view::{fragment_to_tokens, TagType}; use crate::view::{fragment_to_tokens, TagType};
use proc_macro2::{Ident, TokenStream, TokenTree}; use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{format_ident, quote, quote_spanned}; use quote::{format_ident, quote, quote_spanned};
use rstml::node::{KeyedAttribute, NodeAttribute, NodeElement}; use rstml::node::{CustomNode, KeyedAttribute, NodeAttribute, NodeElement};
use std::collections::HashMap; use std::collections::HashMap;
use syn::spanned::Spanned; use syn::spanned::Spanned;
pub(crate) fn slot_to_tokens( pub(crate) fn slot_to_tokens(
node: &NodeElement, node: &NodeElement<impl CustomNode>,
slot: &KeyedAttribute, slot: &KeyedAttribute,
parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>, parent_slots: Option<&mut HashMap<String, Vec<TokenStream>>>,
global_class: Option<&TokenTree>, global_class: Option<&TokenTree>,
@ -32,7 +32,7 @@ pub(crate) fn slot_to_tokens(
let attrs = node.attributes().iter().filter_map(|node| { let attrs = node.attributes().iter().filter_map(|node| {
if let NodeAttribute::Attribute(node) = node { if let NodeAttribute::Attribute(node) = node {
if is_slot(node) { if is_slot(&node) {
None None
} else { } else {
Some(node) Some(node)
@ -213,10 +213,10 @@ pub(crate) fn is_slot(node: &KeyedAttribute) -> bool {
key == "slot" || key.starts_with("slot:") key == "slot" || key.starts_with("slot:")
} }
pub(crate) fn get_slot(node: &NodeElement) -> Option<&KeyedAttribute> { pub(crate) fn get_slot(node: &NodeElement<impl CustomNode>) -> Option<&KeyedAttribute> {
node.attributes().iter().find_map(|node| { node.attributes().iter().find_map(|node| {
if let NodeAttribute::Attribute(node) = node { if let NodeAttribute::Attribute(node) = node {
if is_slot(node) { if is_slot(&node) {
Some(node) Some(node)
} else { } else {
None None