chore: macro `panic` hygiene (#568)
This commit is contained in:
parent
a985ae5660
commit
46e6e7629c
|
@ -348,31 +348,30 @@ impl Docs {
|
|||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, attr)| {
|
||||
if let Meta::NameValue(MetaNameValue { lit: doc, .. }) =
|
||||
attr.parse_meta().unwrap()
|
||||
{
|
||||
let doc_str = quote!(#doc);
|
||||
match attr.parse_meta() {
|
||||
Ok(Meta::NameValue(MetaNameValue { lit: doc, .. })) => {
|
||||
let doc_str = quote!(#doc);
|
||||
|
||||
// We need to remove the leading and trailing `"`"
|
||||
let mut doc_str = doc_str.to_string();
|
||||
doc_str.pop();
|
||||
doc_str.remove(0);
|
||||
// We need to remove the leading and trailing `"`"
|
||||
let mut doc_str = doc_str.to_string();
|
||||
doc_str.pop();
|
||||
doc_str.remove(0);
|
||||
|
||||
let doc_str = if idx == 0 {
|
||||
format!(" - {doc_str}")
|
||||
} else {
|
||||
format!(" {doc_str}")
|
||||
};
|
||||
let doc_str = if idx == 0 {
|
||||
format!(" - {doc_str}")
|
||||
} else {
|
||||
format!(" {doc_str}")
|
||||
};
|
||||
|
||||
let docs = LitStr::new(&doc_str, doc.span());
|
||||
let docs = LitStr::new(&doc_str, doc.span());
|
||||
|
||||
if !doc_str.is_empty() {
|
||||
quote! { #[doc = #docs] }
|
||||
} else {
|
||||
quote! {}
|
||||
if !doc_str.is_empty() {
|
||||
quote! { #[doc = #docs] }
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
_ => abort!(attr, "could not parse attributes"),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
|
@ -384,18 +383,17 @@ impl Docs {
|
|||
.0
|
||||
.iter()
|
||||
.map(|attr| {
|
||||
if let Meta::NameValue(MetaNameValue { lit: doc, .. }) =
|
||||
attr.parse_meta().unwrap()
|
||||
{
|
||||
let mut doc_str = quote!(#doc).to_string();
|
||||
match attr.parse_meta() {
|
||||
Ok(Meta::NameValue(MetaNameValue { lit: doc, .. })) => {
|
||||
let mut doc_str = quote!(#doc).to_string();
|
||||
|
||||
// Remove the leading and trailing `"`
|
||||
doc_str.pop();
|
||||
doc_str.remove(0);
|
||||
// Remove the leading and trailing `"`
|
||||
doc_str.pop();
|
||||
doc_str.remove(0);
|
||||
|
||||
doc_str
|
||||
} else {
|
||||
unreachable!()
|
||||
doc_str
|
||||
}
|
||||
_ => abort!(attr, "could not parse attributes"),
|
||||
}
|
||||
})
|
||||
.intersperse("\n".to_string())
|
||||
|
@ -633,7 +631,7 @@ fn is_option(ty: &Type) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn unwrap_option(ty: &Type) -> Option<Type> {
|
||||
fn unwrap_option(ty: &Type) -> Type {
|
||||
const STD_OPTION_MSG: &str =
|
||||
"make sure you're not shadowing the `std::option::Option` type that \
|
||||
is automatically imported from the standard prelude";
|
||||
|
@ -651,37 +649,19 @@ fn unwrap_option(ty: &Type) -> Option<Type> {
|
|||
{
|
||||
if let [first] = &args.iter().collect::<Vec<_>>()[..] {
|
||||
if let GenericArgument::Type(ty) = first {
|
||||
Some(ty.clone())
|
||||
} else {
|
||||
abort!(
|
||||
first,
|
||||
"`Option` must be `std::option::Option`";
|
||||
help = STD_OPTION_MSG
|
||||
);
|
||||
return ty.clone();
|
||||
}
|
||||
} else {
|
||||
abort!(
|
||||
first,
|
||||
"`Option` must be `std::option::Option`";
|
||||
help = STD_OPTION_MSG
|
||||
);
|
||||
}
|
||||
} else {
|
||||
abort!(
|
||||
first,
|
||||
"`Option` must be `std::option::Option`";
|
||||
help = STD_OPTION_MSG
|
||||
);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
abort!(
|
||||
ty,
|
||||
"`Option` must be `std::option::Option`";
|
||||
help = STD_OPTION_MSG
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -703,7 +683,7 @@ fn prop_to_doc(
|
|||
|| prop_opts.contains(&PropOpt::StripOption))
|
||||
&& is_option(ty)
|
||||
{
|
||||
unwrap_option(ty).unwrap()
|
||||
unwrap_option(ty)
|
||||
} else {
|
||||
ty.to_owned()
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ use proc_macro2::TokenTree;
|
|||
use quote::ToTokens;
|
||||
use server::server_macro_impl;
|
||||
use syn::parse_macro_input;
|
||||
use syn_rsx::{parse, NodeElement};
|
||||
use syn_rsx::{parse, NodeAttribute, NodeElement};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum Mode {
|
||||
|
@ -304,13 +304,10 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
third.clone()
|
||||
}
|
||||
_ => {
|
||||
let error_msg = concat!(
|
||||
"To create a scope class with the view! macro \
|
||||
you must put a comma `,` after the value.\n",
|
||||
"e.g., view!{cx, class=\"my-class\", \
|
||||
<div>...</div>}"
|
||||
);
|
||||
panic!("{error_msg}")
|
||||
abort!(
|
||||
punct, "To create a scope class with the view! macro you must put a comma `,` after the value";
|
||||
help = r#"e.g., view!{cx, class="my-class", <div>...</div>}"#
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -338,7 +335,7 @@ pub fn view(tokens: TokenStream) -> TokenStream {
|
|||
.into()
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
abort_call_site!(
|
||||
"view! macro needs a context and RSX: e.g., view! {{ cx, \
|
||||
<div>...</div> }}"
|
||||
)
|
||||
|
@ -372,7 +369,7 @@ pub fn template(tokens: TokenStream) -> TokenStream {
|
|||
.into()
|
||||
}
|
||||
_ => {
|
||||
panic!(
|
||||
abort_call_site!(
|
||||
"view! macro needs a context and RSX: e.g., view! {{ cx, \
|
||||
<div>...</div> }}"
|
||||
)
|
||||
|
@ -693,8 +690,10 @@ pub fn server(args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
|
|||
pub fn params_derive(
|
||||
input: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
let ast = syn::parse(input).unwrap();
|
||||
params::impl_params(&ast)
|
||||
match syn::parse(input) {
|
||||
Ok(ast) => params::impl_params(&ast),
|
||||
Err(err) => err.to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_component_node(node: &NodeElement) -> bool {
|
||||
|
@ -702,3 +701,10 @@ pub(crate) fn is_component_node(node: &NodeElement) -> bool {
|
|||
.to_string()
|
||||
.starts_with(|c: char| c.is_ascii_uppercase())
|
||||
}
|
||||
|
||||
pub(crate) fn attribute_value(attr: &NodeAttribute) -> &syn::Expr {
|
||||
match &attr.value {
|
||||
Some(value) => value.as_ref(),
|
||||
None => abort!(attr.key, "attribute should have value"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,10 @@ pub fn impl_params(ast: &syn::DeriveInput) -> proc_macro::TokenStream {
|
|||
.named
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let field_name_string = &field.ident.as_ref().unwrap().to_string();
|
||||
let field_name_string = &field.ident.as_ref().expect("expected named struct fields").to_string();
|
||||
let ident = &field.ident;
|
||||
let ty = &field.ty;
|
||||
let span = field.span().unwrap();
|
||||
let span = field.span();
|
||||
|
||||
quote_spanned! {
|
||||
span.into() => #ident: <#ty>::into_param(map.get(#field_name_string).map(|n| n.as_str()), #field_name_string)?
|
||||
|
|
|
@ -61,7 +61,7 @@ pub fn server_macro_impl(
|
|||
let fields = body.inputs.iter().filter(|f| !fn_arg_is_cx(f)).map(|f| {
|
||||
let typed_arg = match f {
|
||||
FnArg::Receiver(_) => {
|
||||
panic!("cannot use receiver types in server function macro")
|
||||
abort!(f, "cannot use receiver types in server function macro")
|
||||
}
|
||||
FnArg::Typed(t) => t,
|
||||
};
|
||||
|
@ -96,7 +96,7 @@ pub fn server_macro_impl(
|
|||
let fn_args = body.inputs.iter().map(|f| {
|
||||
let typed_arg = match f {
|
||||
FnArg::Receiver(_) => {
|
||||
panic!("cannot use receiver types in server function macro")
|
||||
abort!(f, "cannot use receiver types in server function macro")
|
||||
}
|
||||
FnArg::Typed(t) => t,
|
||||
};
|
||||
|
@ -131,22 +131,21 @@ pub fn server_macro_impl(
|
|||
let output_arrow = body.output_arrow;
|
||||
let return_ty = body.return_ty;
|
||||
|
||||
let output_ty = if let syn::Type::Path(pat) = &return_ty {
|
||||
if pat.path.segments[0].ident == "Result" {
|
||||
if let PathArguments::AngleBracketed(args) =
|
||||
&pat.path.segments[0].arguments
|
||||
{
|
||||
&args.args[0]
|
||||
} else {
|
||||
panic!(
|
||||
"server functions should return Result<T, ServerFnError>"
|
||||
);
|
||||
let output_ty = 'output_ty: {
|
||||
if let syn::Type::Path(pat) = &return_ty {
|
||||
if pat.path.segments[0].ident == "Result" {
|
||||
if let PathArguments::AngleBracketed(args) =
|
||||
&pat.path.segments[0].arguments
|
||||
{
|
||||
break 'output_ty &args.args[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("server functions should return Result<T, ServerFnError>");
|
||||
}
|
||||
} else {
|
||||
panic!("server functions should return Result<T, ServerFnError>");
|
||||
|
||||
abort!(
|
||||
return_ty,
|
||||
"server functions should return Result<T, ServerFnError>"
|
||||
);
|
||||
};
|
||||
|
||||
Ok(quote::quote! {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::is_component_node;
|
||||
use crate::{attribute_value, is_component_node};
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::{quote, quote_spanned};
|
||||
use syn::spanned::Spanned;
|
||||
|
@ -11,21 +11,11 @@ pub(crate) fn render_template(cx: &Ident, nodes: &[Node]) -> TokenStream {
|
|||
Span::call_site(),
|
||||
);
|
||||
|
||||
if nodes.len() == 1 {
|
||||
first_node_to_tokens(cx, &template_uid, &nodes[0])
|
||||
} else {
|
||||
panic!("template! takes a single root element.")
|
||||
}
|
||||
}
|
||||
|
||||
fn first_node_to_tokens(
|
||||
cx: &Ident,
|
||||
template_uid: &Ident,
|
||||
node: &Node,
|
||||
) -> TokenStream {
|
||||
match node {
|
||||
Node::Element(node) => root_element_to_tokens(cx, template_uid, node),
|
||||
_ => panic!("template! takes a single root element."),
|
||||
match nodes.first() {
|
||||
Some(Node::Element(node)) => {
|
||||
root_element_to_tokens(cx, &template_uid, node)
|
||||
}
|
||||
_ => abort!(cx, "template! takes a single root element."),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,7 +195,11 @@ fn element_to_tokens(
|
|||
let mut prev_sib = prev_sib;
|
||||
for (idx, child) in node.children.iter().enumerate() {
|
||||
// set next sib (for any insertions)
|
||||
let next_sib = next_sibling_node(&node.children, idx + 1, next_el_id);
|
||||
let next_sib =
|
||||
match next_sibling_node(&node.children, idx + 1, next_el_id) {
|
||||
Ok(next_sib) => next_sib,
|
||||
Err(err) => abort!(span, "{}", err),
|
||||
};
|
||||
|
||||
let curr_id = child_to_tokens(
|
||||
cx,
|
||||
|
@ -239,9 +233,9 @@ fn next_sibling_node(
|
|||
children: &[Node],
|
||||
idx: usize,
|
||||
next_el_id: &mut usize,
|
||||
) -> Option<Ident> {
|
||||
) -> Result<Option<Ident>, String> {
|
||||
if children.len() <= idx {
|
||||
None
|
||||
Ok(None)
|
||||
} else {
|
||||
let sibling = &children[idx];
|
||||
|
||||
|
@ -250,16 +244,16 @@ fn next_sibling_node(
|
|||
if is_component_node(sibling) {
|
||||
next_sibling_node(children, idx + 1, next_el_id)
|
||||
} else {
|
||||
Some(child_ident(*next_el_id + 1, sibling.name.span()))
|
||||
Ok(Some(child_ident(*next_el_id + 1, sibling.name.span())))
|
||||
}
|
||||
}
|
||||
Node::Block(sibling) => {
|
||||
Some(child_ident(*next_el_id + 1, sibling.value.span()))
|
||||
Ok(Some(child_ident(*next_el_id + 1, sibling.value.span())))
|
||||
}
|
||||
Node::Text(sibling) => {
|
||||
Some(child_ident(*next_el_id + 1, sibling.value.span()))
|
||||
Ok(Some(child_ident(*next_el_id + 1, sibling.value.span())))
|
||||
}
|
||||
_ => panic!("expected either an element or a block"),
|
||||
_ => Err("expected either an element or a block".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -272,16 +266,9 @@ fn attr_to_tokens(
|
|||
expressions: &mut Vec<TokenStream>,
|
||||
) {
|
||||
let name = node.key.to_string();
|
||||
let name = if name.starts_with('_') {
|
||||
name.replacen('_', "", 1)
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let name = if name.starts_with("attr:") {
|
||||
name.replacen("attr:", "", 1)
|
||||
} else {
|
||||
name
|
||||
};
|
||||
let name = name.strip_prefix("_").unwrap_or(&name);
|
||||
let name = name.strip_prefix("attr:").unwrap_or(&name);
|
||||
|
||||
let value = match &node.value {
|
||||
Some(expr) => match expr.as_ref() {
|
||||
syn::Expr::Lit(expr_lit) => {
|
||||
|
@ -300,7 +287,7 @@ fn attr_to_tokens(
|
|||
|
||||
// refs
|
||||
if name == "ref" {
|
||||
panic!("node_ref not yet supported in template! macro")
|
||||
abort!(span, "node_ref not yet supported in template! macro")
|
||||
}
|
||||
// Event Handlers
|
||||
else if name.starts_with("on:") {
|
||||
|
@ -311,28 +298,20 @@ fn attr_to_tokens(
|
|||
})
|
||||
}
|
||||
// Properties
|
||||
else if name.starts_with("prop:") {
|
||||
let name = name.replacen("prop:", "", 1);
|
||||
let value = node
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("prop: blocks need values")
|
||||
.as_ref();
|
||||
else if let Some(name) = name.strip_prefix("prop:") {
|
||||
let value = attribute_value(&node);
|
||||
|
||||
expressions.push(quote_spanned! {
|
||||
span => leptos_dom::property(#cx, #el_id.unchecked_ref(), #name, #value.into_property(#cx))
|
||||
});
|
||||
span => leptos_dom::property(#cx, #el_id.unchecked_ref(), #name, #value.into_property(#cx))
|
||||
});
|
||||
}
|
||||
// Classes
|
||||
else if name.starts_with("class:") {
|
||||
let name = name.replacen("class:", "", 1);
|
||||
let value = node
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("class: attributes need values")
|
||||
.as_ref();
|
||||
else if let Some(name) = name.strip_prefix("class:") {
|
||||
let value = attribute_value(&node);
|
||||
|
||||
expressions.push(quote_spanned! {
|
||||
span => leptos::leptos_dom::class_helper(#el_id.unchecked_ref(), #name.into(), #value.into_class(#cx))
|
||||
});
|
||||
span => leptos::leptos_dom::class_helper(#el_id.unchecked_ref(), #name.into(), #value.into_class(#cx))
|
||||
});
|
||||
}
|
||||
// Attributes
|
||||
else {
|
||||
|
@ -429,7 +408,7 @@ fn child_to_tokens(
|
|||
expressions,
|
||||
navigations,
|
||||
),
|
||||
_ => panic!("unexpected child node type"),
|
||||
_ => abort!(cx, "unexpected child node type"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{is_component_node, Mode};
|
||||
use crate::{attribute_value, is_component_node, Mode};
|
||||
use proc_macro2::{Ident, Span, TokenStream, TokenTree};
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::{spanned::Spanned, Expr, ExprLit, ExprPath, Lit};
|
||||
|
@ -576,11 +576,7 @@ fn set_class_attribute_ssr(
|
|||
} else {
|
||||
name
|
||||
};
|
||||
let value = node
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("class: attributes need values")
|
||||
.as_ref();
|
||||
let value = attribute_value(node);
|
||||
let span = node.key.span();
|
||||
Some((span, name, value))
|
||||
} else {
|
||||
|
@ -802,23 +798,14 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
let span = node.key.span();
|
||||
let name = node.key.to_string();
|
||||
if name == "ref" || name == "_ref" || name == "ref_" || name == "node_ref" {
|
||||
let value = node
|
||||
.value
|
||||
.as_ref()
|
||||
.and_then(|expr| expr_to_ident(expr))
|
||||
.expect("'_ref' needs to be passed a variable name");
|
||||
let value = expr_to_ident(attribute_value(node));
|
||||
let node_ref = quote_spanned! { span => node_ref };
|
||||
|
||||
quote! {
|
||||
.#node_ref(#value)
|
||||
}
|
||||
} else if let Some(name) = name.strip_prefix("on:") {
|
||||
let handler = node
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("event listener attributes need a value")
|
||||
.as_ref();
|
||||
|
||||
let handler = attribute_value(node);
|
||||
let (name, is_force_undelegated) = parse_event(name);
|
||||
|
||||
let event_type = TYPED_EVENTS
|
||||
|
@ -827,9 +814,10 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
.copied()
|
||||
.unwrap_or("Custom");
|
||||
let is_custom = event_type == "Custom";
|
||||
let event_type = event_type
|
||||
.parse::<TokenStream>()
|
||||
.expect("couldn't parse event name");
|
||||
|
||||
let Ok(event_type) = event_type.parse::<TokenStream>() else {
|
||||
abort!(event_type, "couldn't parse event name");
|
||||
};
|
||||
|
||||
let event_type = if is_custom {
|
||||
quote! { leptos::ev::Custom::new(#name) }
|
||||
|
@ -896,11 +884,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
#on(#event_type, #handler)
|
||||
}
|
||||
} else if let Some(name) = name.strip_prefix("prop:") {
|
||||
let value = node
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("prop: attributes need a value")
|
||||
.as_ref();
|
||||
let value = attribute_value(node);
|
||||
let prop = match &node.key {
|
||||
NodeName::Punctuated(parts) => &parts[0],
|
||||
_ => unreachable!(),
|
||||
|
@ -915,11 +899,7 @@ fn attribute_to_tokens(cx: &Ident, node: &NodeAttribute) -> TokenStream {
|
|||
#prop(#name, (#cx, #[allow(unused_braces)] #value))
|
||||
}
|
||||
} else if let Some(name) = name.strip_prefix("class:") {
|
||||
let value = node
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("class: attributes need a value")
|
||||
.as_ref();
|
||||
let value = attribute_value(node);
|
||||
let class = match &node.key {
|
||||
NodeName::Punctuated(parts) => &parts[0],
|
||||
_ => unreachable!(),
|
||||
|
@ -1012,16 +992,11 @@ pub(crate) fn component_to_tokens(
|
|||
|
||||
let items_to_clone = attrs
|
||||
.clone()
|
||||
.filter(|attr| attr.key.to_string().starts_with("clone:"))
|
||||
.map(|attr| {
|
||||
let ident = attr
|
||||
.key
|
||||
.filter_map(|attr| {
|
||||
attr.key
|
||||
.to_string()
|
||||
.strip_prefix("clone:")
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
|
||||
format_ident!("{ident}", span = attr.key.span())
|
||||
.map(|ident| format_ident!("{ident}", span = attr.key.span()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -1085,14 +1060,14 @@ pub(crate) fn event_from_attribute_node(
|
|||
attr: &NodeAttribute,
|
||||
force_undelegated: bool,
|
||||
) -> (TokenStream, &Expr) {
|
||||
let event_name =
|
||||
attr.key.to_string().strip_prefix("on:").unwrap().to_owned();
|
||||
let event_name = attr
|
||||
.key
|
||||
.to_string()
|
||||
.strip_prefix("on:")
|
||||
.expect("expected `on:` directive")
|
||||
.to_owned();
|
||||
|
||||
let handler = attr
|
||||
.value
|
||||
.as_ref()
|
||||
.expect("event listener attributes need a value")
|
||||
.as_ref();
|
||||
let handler = attribute_value(attr);
|
||||
|
||||
#[allow(unused_variables)]
|
||||
let (name, name_undelegated) = parse_event(&event_name);
|
||||
|
@ -1102,9 +1077,10 @@ pub(crate) fn event_from_attribute_node(
|
|||
.find(|e| **e == name)
|
||||
.copied()
|
||||
.unwrap_or("Custom");
|
||||
let event_type = event_type
|
||||
.parse::<TokenStream>()
|
||||
.expect("couldn't parse event name");
|
||||
|
||||
let Ok(event_type) = event_type.parse::<TokenStream>() else {
|
||||
abort!(attr.key, "couldn't parse event name");
|
||||
};
|
||||
|
||||
let event_type = if force_undelegated || name_undelegated {
|
||||
quote! { ::leptos::leptos_dom::ev::undelegated(::leptos::leptos_dom::ev::#event_type) }
|
||||
|
|
Loading…
Reference in New Issue