Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rsx parser with recovery after errors, and unquoted text #1054

Merged
merged 4 commits into from
May 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions leptos_hot_reload/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ readme = "../README.md"
[dependencies]
anyhow = "1"
serde = { version = "1", features = ["derive"] }
syn = { version = "1", features = [
syn = { version = "2", features = [
"full",
"parsing",
"extra-traits",
"visit",
"printing",
] }
quote = "1"
syn-rsx = "0.9"
rstml = "0.10.6"
proc-macro2 = { version = "1", features = ["span-locations", "nightly"] }
parking_lot = "0.12"
walkdir = "2"
Expand Down
2 changes: 1 addition & 1 deletion leptos_hot_reload/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ impl ViewMacros {
tokens.next(); // ,
// TODO handle class = ...
let rsx =
syn_rsx::parse2(tokens.collect::<proc_macro2::TokenStream>())?;
rstml::parse2(tokens.collect::<proc_macro2::TokenStream>())?;
let template = LNode::parse_view(rsx)?;
views.push(MacroInvocation { id, template })
}
Expand Down
36 changes: 14 additions & 22 deletions leptos_hot_reload/src/node.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::parsing::{is_component_node, value_to_string};
use crate::parsing::is_component_node;
use anyhow::Result;
use quote::quote;
use quote::ToTokens;
use rstml::node::{Node, NodeAttribute};
use serde::{Deserialize, Serialize};
use syn_rsx::Node;

// A lightweight virtual DOM structure we can use to hold
// the state of a Leptos view macro template. This is because
Expand Down Expand Up @@ -58,52 +58,44 @@ impl LNode {
}
}
Node::Text(text) => {
if let Some(value) = value_to_string(&text.value) {
views.push(LNode::Text(value));
} else {
let value = text.value.as_ref();
let code = quote! { #value };
let code = code.to_string();
views.push(LNode::DynChild(code));
}
views.push(LNode::Text(text.value_string()));
}
Node::Block(block) => {
let value = block.value.as_ref();
let code = quote! { #value };
let code = block.into_token_stream();
let code = code.to_string();
views.push(LNode::DynChild(code));
}
Node::Element(el) => {
if is_component_node(&el) {
let name = el.name().to_string();
let mut children = Vec::new();
for child in el.children {
LNode::parse_node(child, &mut children)?;
}
views.push(LNode::Component {
name: el.name.to_string(),
name: name,
props: el
.open_tag
.attributes
.into_iter()
.filter_map(|attr| match attr {
Node::Attribute(attr) => Some((
NodeAttribute::Attribute(attr) => Some((
attr.key.to_string(),
format!("{:#?}", attr.value),
format!("{:#?}", attr.value()),
)),
_ => None,
})
.collect(),
children,
});
} else {
let name = el.name.to_string();
let name = el.name().to_string();
let mut attrs = Vec::new();

for attr in el.attributes {
if let Node::Attribute(attr) = attr {
for attr in el.open_tag.attributes {
if let NodeAttribute::Attribute(attr) = attr {
let name = attr.key.to_string();
if let Some(value) =
attr.value.as_ref().and_then(value_to_string)
{
if let Some(value) = attr.value_literal_string() {
attrs.push((
name,
LAttributeValue::Static(value),
Expand Down
38 changes: 34 additions & 4 deletions leptos_hot_reload/src/parsing.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,37 @@
use syn_rsx::{NodeElement, NodeValueExpr};
use rstml::node::NodeElement;

pub fn value_to_string(value: &NodeValueExpr) -> Option<String> {
match &value.as_ref() {
///
/// Converts `syn::Block` to simple expression
///
/// For example:
/// ```no_build
/// // "string literal" in
/// {"string literal"}
/// // number literal
/// {0x12}
/// // boolean literal
/// {true}
/// // variable
/// {path::x}
/// ```
pub fn block_to_primitive_expression(block: &syn::Block) -> Option<&syn::Expr> {
// its empty block, or block with multi lines
if block.stmts.len() != 1 {
return None;
}
match &block.stmts[0] {
syn::Stmt::Expr(e, None) => return Some(&e),
_ => {}
}
None
}

/// Converts simple literals to its string representation.
///
/// This function doesn't convert literal wrapped inside block
/// like: `{"string"}`.
pub fn value_to_string(value: &syn::Expr) -> Option<String> {
match &value {
syn::Expr::Lit(lit) => match &lit.lit {
syn::Lit::Str(s) => Some(s.value()),
syn::Lit::Char(c) => Some(c.value().to_string()),
Expand All @@ -14,7 +44,7 @@ pub fn value_to_string(value: &NodeValueExpr) -> Option<String> {
}

pub fn is_component_node(node: &NodeElement) -> bool {
node.name
node.name()
.to_string()
.starts_with(|c: char| c.is_ascii_uppercase())
}
8 changes: 4 additions & 4 deletions leptos_macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ readme = "../README.md"
proc-macro = true

[dependencies]
attribute-derive = { version = "0.5", features = ["syn-full"] }
attribute-derive = { version = "0.6", features = ["syn-full"] }
cfg-if = "1"
html-escape = "0.2"
itertools = "0.10"
prettyplease = "0.1"
prettyplease = "0.2.4"
proc-macro-error = "1"
proc-macro2 = "1"
quote = "1"
syn = { version = "1", features = ["full"] }
syn-rsx = "0.9"
syn = { version = "2", features = ["full"] }
rstml = "0.10.6"
leptos_hot_reload = { workspace = true }
server_fn_macro = { workspace = true }
convert_case = "0.6.0"
Expand Down
37 changes: 24 additions & 13 deletions leptos_macro/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use convert_case::{
Casing,
};
use itertools::Itertools;
use leptos_hot_reload::parsing::value_to_string;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote_spanned, ToTokens, TokenStreamExt};
use syn::{
parse::Parse, parse_quote, spanned::Spanned,
AngleBracketedGenericArguments, Attribute, FnArg, GenericArgument, Item,
ItemFn, Lit, LitStr, Meta, MetaNameValue, Pat, PatIdent, Path,
PathArguments, ReturnType, Stmt, Type, TypePath, Visibility,
ItemFn, LitStr, Meta, Pat, PatIdent, Path, PathArguments, ReturnType, Stmt,
Type, TypePath, Visibility,
};

pub struct Model {
is_transparent: bool,
docs: Docs,
Expand Down Expand Up @@ -56,14 +56,17 @@ impl Parse for Model {

// We need to remove the `#[doc = ""]` and `#[builder(_)]`
// attrs from the function signature
drain_filter(&mut item.attrs, |attr| {
attr.path == parse_quote!(doc) || attr.path == parse_quote!(prop)
drain_filter(&mut item.attrs, |attr| match &attr.meta {
Meta::NameValue(attr) => attr.path == parse_quote!(doc),
Meta::List(attr) => attr.path == parse_quote!(prop),
_ => false,
});
item.sig.inputs.iter_mut().for_each(|arg| {
if let FnArg::Typed(ty) = arg {
drain_filter(&mut ty.attrs, |attr| {
attr.path == parse_quote!(doc)
|| attr.path == parse_quote!(prop)
drain_filter(&mut ty.attrs, |attr| match &attr.meta {
Meta::NameValue(attr) => attr.path == parse_quote!(doc),
Meta::List(attr) => attr.path == parse_quote!(prop),
_ => false,
});
}
});
Expand Down Expand Up @@ -400,12 +403,20 @@ impl Docs {

let mut attrs = attrs
.iter()
.filter_map(|attr| attr.path.is_ident("doc").then(|| {
let Ok(Meta::NameValue(MetaNameValue { lit: Lit::Str(doc), .. })) = attr.parse_meta() else {
abort!(attr, "expected doc comment to be string literal");
.filter_map(|attr| {
let Meta::NameValue(attr ) = &attr.meta else {
return None
};
if !attr.path.is_ident("doc") {
return None
}

let Some(val) = value_to_string(&attr.value) else {
abort!(attr, "expected string literal in value of doc comment");
};
(doc.value(), doc.span())
}))

Some((val, attr.path.span()))
})
.flat_map(map)
.collect_vec();

Expand Down
34 changes: 20 additions & 14 deletions leptos_macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ extern crate proc_macro_error;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenTree};
use quote::ToTokens;
use rstml::{node::KeyedAttribute, parse};
use server_fn_macro::{server_macro_impl, ServerContext};
use syn::parse_macro_input;
use syn_rsx::{parse, NodeAttribute};

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Mode {
Expand Down Expand Up @@ -351,16 +351,22 @@ pub fn view(tokens: TokenStream) -> TokenStream {
.chain(tokens)
.collect()
};

match parse(tokens.into()) {
Ok(nodes) => render_view(
&proc_macro2::Ident::new(&cx.to_string(), cx.span()),
&nodes,
Mode::default(),
global_class.as_ref(),
normalized_call_site(proc_macro::Span::call_site()),
),
Err(error) => error.to_compile_error(),
let config = rstml::ParserConfig::default().recover_block(true);
let parser = rstml::Parser::new(config);
let (nodes, errors) = parser.parse_recoverable(tokens).split_vec();
let errors = errors.into_iter().map(|e| e.emit_as_expr_tokens());
let nodes_output = render_view(
&cx,
&nodes,
Mode::default(),
global_class.as_ref(),
normalized_call_site(proc_macro::Span::call_site()),
);
quote! {
{
#(#errors;)*
#nodes_output
}
}
.into()
}
Expand Down Expand Up @@ -874,9 +880,9 @@ pub fn params_derive(
}
}

pub(crate) fn attribute_value(attr: &NodeAttribute) -> &syn::Expr {
match &attr.value {
Some(value) => value.as_ref(),
pub(crate) fn attribute_value(attr: &KeyedAttribute) -> &syn::Expr {
match &attr.possible_value {
Some(value) => &value.value,
None => abort!(attr.key, "attribute should have value"),
}
}
16 changes: 10 additions & 6 deletions leptos_macro/src/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use attribute_derive::Attribute as AttributeDerive;
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, TokenStreamExt};
use syn::{
parse::Parse, parse_quote, Field, ItemStruct, LitStr, Type, Visibility,
parse::Parse, parse_quote, Field, ItemStruct, LitStr, Meta, Type,
Visibility,
};

pub struct Model {
Expand All @@ -31,13 +32,16 @@ impl Parse for Model {

// We need to remove the `#[doc = ""]` and `#[builder(_)]`
// attrs from the function signature
drain_filter(&mut item.attrs, |attr| {
attr.path == parse_quote!(doc) || attr.path == parse_quote!(prop)
drain_filter(&mut item.attrs, |attr| match &attr.meta {
Meta::NameValue(attr) => attr.path == parse_quote!(doc),
Meta::List(attr) => attr.path == parse_quote!(prop),
_ => false,
});
item.fields.iter_mut().for_each(|arg| {
drain_filter(&mut arg.attrs, |attr| {
attr.path == parse_quote!(doc)
|| attr.path == parse_quote!(prop)
drain_filter(&mut arg.attrs, |attr| match &attr.meta {
Meta::NameValue(attr) => attr.path == parse_quote!(doc),
Meta::List(attr) => attr.path == parse_quote!(prop),
_ => false,
});
});

Expand Down
Loading