Skip to content

Commit

Permalink
Support dashed tag labels
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry committed Jul 3, 2019
1 parent 6e152f5 commit 8962b77
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 49 deletions.
15 changes: 9 additions & 6 deletions crates/macro-impl/src/html_tree/html_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ impl ToTokens for HtmlComponent {
let vcomp_props = Ident::new("__yew_vcomp_props", Span::call_site());
let override_props = props.iter().map(|props| match props {
Props::List(ListProps(vec_props)) => {
let check_props = vec_props.iter().map(|HtmlProp { name, .. }| {
quote_spanned! { name.span()=> #vcomp_props.#name; }
let check_props = vec_props.iter().map(|HtmlProp { label, .. }| {
quote_spanned! { label.span()=> #vcomp_props.#label; }
});

let set_props = vec_props.iter().map(|HtmlProp { name, value }| {
let set_props = vec_props.iter().map(|HtmlProp { label, value }| {
quote_spanned! { value.span()=>
#vcomp_props.#name = ::yew::virtual_dom::vcomp::Transformer::transform(&mut #vcomp, #value);
#vcomp_props.#label = ::yew::virtual_dom::vcomp::Transformer::transform(&mut #vcomp, #value);
}
});

Expand Down Expand Up @@ -192,8 +192,11 @@ impl Parse for ListProps {
}

for prop in &props {
if prop.name.to_string() == "type" {
return Err(syn::Error::new_spanned(&prop.name, "expected identifier"));
if prop.label.to_string() == "type" {
return Err(syn::Error::new_spanned(&prop.label, "expected identifier"));
}
if !prop.label.extended.is_empty() {
return Err(syn::Error::new_spanned(&prop.label, "expected identifier"));
}
}

Expand Down
82 changes: 68 additions & 14 deletions crates/macro-impl/src/html_tree/html_prop.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,40 @@
use crate::Peek;
use boolinator::Boolinator;
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use proc_macro2::{Ident, TokenTree};
use quote::{quote, ToTokens};
use std::fmt;
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::{Expr, Ident, Token};
use syn::{Expr, Token};

pub struct HtmlProp {
pub name: Ident,
pub label: HtmlPropLabel,
pub value: Expr,
}

impl Peek<()> for HtmlProp {
fn peek(cursor: Cursor) -> Option<()> {
let (_, cursor) = cursor.ident()?;
let (punct, _) = cursor.punct()?;
(punct.as_char() == '=').as_option()
fn peek(mut cursor: Cursor) -> Option<()> {
loop {
let (_, c) = cursor.ident()?;
let (punct, c) = c.punct()?;
if punct.as_char() == '-' {
cursor = c;
continue;
}
return (punct.as_char() == '=').as_option();
}
}
}

impl Parse for HtmlProp {
fn parse(input: ParseStream) -> ParseResult<Self> {
let name = if let Ok(ty) = input.parse::<Token![type]>() {
Ident::new("type", ty.span)
} else {
input.parse::<Ident>()?
};

let label = input.parse::<HtmlPropLabel>()?;
input.parse::<Token![=]>()?;
let value = input.parse::<Expr>()?;
// backwards compat
let _ = input.parse::<Token![,]>();
Ok(HtmlProp { name, value })
Ok(HtmlProp { label, value })
}
}

Expand Down Expand Up @@ -84,3 +87,54 @@ impl Parse for HtmlPropSuffix {
Ok(HtmlPropSuffix { div, gt, stream })
}
}

pub struct HtmlPropLabel {
pub name: Ident,
pub extended: Vec<(Token![-], Ident)>,
}

impl HtmlPropLabel {
pub fn new(name: Ident) -> Self {
HtmlPropLabel {
name,
extended: Vec::new(),
}
}
}

impl fmt::Display for HtmlPropLabel {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.name)?;
for (_, ident) in &self.extended {
write!(f, "-{}", ident)?;
}
Ok(())
}
}

impl Parse for HtmlPropLabel {
fn parse(input: ParseStream) -> ParseResult<Self> {
let name = if let Ok(ty) = input.parse::<Token![type]>() {
Ident::new("type", ty.span).into()
} else {
input.parse::<Ident>()?.into()
};

let mut extended = Vec::new();
while input.peek(Token![-]) {
extended.push((input.parse::<Token![-]>()?, input.parse::<Ident>()?));
}

Ok(HtmlPropLabel { name, extended })
}
}

impl ToTokens for HtmlPropLabel {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let HtmlPropLabel { name, extended } = self;
let dashes = extended.iter().map(|(dash, _)| quote! {#dash});
let idents = extended.iter().map(|(_, ident)| quote! {#ident});
let extended = quote! { #(#dashes#idents)* };
tokens.extend(quote! {#name#extended});
}
}
7 changes: 4 additions & 3 deletions crates/macro-impl/src/html_tree/html_tag/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod tag_attributes;

use super::HtmlProp as TagAttribute;
use super::HtmlPropLabel as TagLabel;
use super::HtmlPropSuffix as TagSuffix;
use super::HtmlTree;
use crate::Peek;
Expand Down Expand Up @@ -100,7 +101,7 @@ impl ToTokens for HtmlTag {
} = &attributes;

let vtag = Ident::new("__yew_vtag", ident.span());
let attr_names = attributes.iter().map(|attr| attr.name.to_string());
let attr_labels = attributes.iter().map(|attr| attr.label.to_string());
let attr_values = attributes.iter().map(|attr| &attr.value);
let set_kind = kind.iter().map(|kind| {
quote_spanned! {kind.span()=> #vtag.set_kind(&(#kind)); }
Expand Down Expand Up @@ -149,7 +150,7 @@ impl ToTokens for HtmlTag {
#(#add_disabled)*
#(#add_selected)*
#(#set_classes)*
#vtag.add_attributes(vec![#((#attr_names.to_owned(), (#attr_values).to_string())),*]);
#vtag.add_attributes(vec![#((#attr_labels.to_owned(), (#attr_values).to_string())),*]);
#vtag.add_listeners(vec![#(::std::boxed::Box::new(#listeners)),*]);
#vtag.add_children(vec![#(#children),*]);
::yew::virtual_dom::VNode::VTag(#vtag)
Expand Down Expand Up @@ -217,7 +218,7 @@ impl Parse for HtmlTagOpen {
_ => {
if let Some(value) = attributes.value.take() {
attributes.attributes.push(TagAttribute {
name: Ident::new("value", Span::call_site()),
label: TagLabel::new(Ident::new("value", Span::call_site())),
value,
});
}
Expand Down
23 changes: 14 additions & 9 deletions crates/macro-impl/src/html_tree/html_tag/tag_attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ impl TagAttributes {
let mut i = 0;
let mut drained = Vec::new();
while i < attrs.len() {
let name_str = attrs[i].name.to_string();
let name_str = attrs[i].label.to_string();
if let Some(event_type) = LISTENER_MAP.get(&name_str.as_str()) {
let TagAttribute { name, value } = attrs.remove(i);
let TagAttribute { label, value } = attrs.remove(i);
drained.push(TagListener {
name,
name: label.name,
handler: value,
event_name: event_type.to_owned().to_string(),
});
Expand All @@ -98,7 +98,7 @@ impl TagAttributes {
fn remove_attr(attrs: &mut Vec<TagAttribute>, name: &str) -> Option<Expr> {
let mut i = 0;
while i < attrs.len() {
if attrs[i].name.to_string() == name {
if attrs[i].label.to_string() == name {
return Some(attrs.remove(i).value);
} else {
i += 1;
Expand Down Expand Up @@ -182,14 +182,19 @@ impl Parse for TagAttributes {
}

// Multiple listener attributes are allowed, but no others
attributes.sort_by(|a, b| a.name.to_string().partial_cmp(&b.name.to_string()).unwrap());
attributes.sort_by(|a, b| {
a.label
.to_string()
.partial_cmp(&b.label.to_string())
.unwrap()
});
let mut i = 0;
while i + 1 < attributes.len() {
if attributes[i].name.to_string() == attributes[i + 1].name.to_string() {
let name = &attributes[i + 1].name;
if attributes[i].label.to_string() == attributes[i + 1].label.to_string() {
let label = &attributes[i + 1].label;
return Err(syn::Error::new_spanned(
name,
format!("only one `{}` attribute allowed", name),
label,
format!("only one `{}` attribute allowed", label),
));
}
i += 1;
Expand Down
1 change: 1 addition & 0 deletions crates/macro-impl/src/html_tree/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use html_iterable::HtmlIterable;
use html_list::HtmlList;
use html_node::HtmlNode;
use html_prop::HtmlProp;
use html_prop::HtmlPropLabel;
use html_prop::HtmlPropSuffix;
use html_tag::HtmlTag;
use proc_macro2::TokenStream;
Expand Down
4 changes: 4 additions & 0 deletions crates/macro/tests/html-component-fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ test_html! { |t8|
<ChildComponent with props () />
}

test_html! { |t9|
<ChildComponent invalid-prop-name=0 />
}

test_html! { |t10|
<String />
}
Expand Down
38 changes: 22 additions & 16 deletions crates/macro/tests/html-component-fail.stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
error: expected identifier
--> $DIR/html-component-fail.rs:65:21
|
65 | <ChildComponent invalid-prop-name=0 />
| ^^^^^^^^^^^^^^^^^

error: unexpected token
--> $DIR/html-component-fail.rs:61:32
|
Expand Down Expand Up @@ -47,48 +53,48 @@ error: expected component tag be of form `< .. />`
| ^^^^^^^^^^^^^^^^

error[E0425]: cannot find value `blah` in this scope
--> $DIR/html-component-fail.rs:69:26
--> $DIR/html-component-fail.rs:73:26
|
69 | <ChildComponent with blah />
73 | <ChildComponent with blah />
| ^^^^ not found in this scope

error[E0277]: the trait bound `std::string::String: yew_shared::html::Component` is not satisfied
--> $DIR/html-component-fail.rs:65:6
--> $DIR/html-component-fail.rs:69:6
|
65 | <String />
69 | <String />
| ^^^^^^ the trait `yew_shared::html::Component` is not implemented for `std::string::String`
|
= note: required by `yew_shared::virtual_dom::vcomp::VComp::<COMP>::lazy`

error[E0277]: the trait bound `std::string::String: yew_shared::html::Renderable<std::string::String>` is not satisfied
--> $DIR/html-component-fail.rs:65:6
--> $DIR/html-component-fail.rs:69:6
|
65 | <String />
69 | <String />
| ^^^^^^ the trait `yew_shared::html::Renderable<std::string::String>` is not implemented for `std::string::String`
|
= note: required by `yew_shared::virtual_dom::vcomp::VComp::<COMP>::lazy`

error[E0609]: no field `unknown` on type `ChildProperties`
--> $DIR/html-component-fail.rs:73:21
--> $DIR/html-component-fail.rs:77:21
|
73 | <ChildComponent unknown="unknown" />
77 | <ChildComponent unknown="unknown" />
| ^^^^^^^ unknown field
|
= note: available fields are: `string`, `int`

error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:77:28
--> $DIR/html-component-fail.rs:81:28
|
77 | <ChildComponent string={} />
81 | <ChildComponent string={} />
| ^^ expected struct `std::string::String`, found ()
|
= note: expected type `std::string::String`
found type `()`

error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:81:28
--> $DIR/html-component-fail.rs:85:28
|
81 | <ChildComponent string=3 />
85 | <ChildComponent string=3 />
| ^
| |
| expected struct `std::string::String`, found integer
Expand All @@ -98,9 +104,9 @@ error[E0308]: mismatched types
found type `{integer}`

error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:85:28
--> $DIR/html-component-fail.rs:89:28
|
85 | <ChildComponent string={3} />
89 | <ChildComponent string={3} />
| ^^^
| |
| expected struct `std::string::String`, found integer
Expand All @@ -110,9 +116,9 @@ error[E0308]: mismatched types
found type `{integer}`

error[E0308]: mismatched types
--> $DIR/html-component-fail.rs:89:25
--> $DIR/html-component-fail.rs:93:25
|
89 | <ChildComponent int=0u32 />
93 | <ChildComponent int=0u32 />
| ^^^^ expected i32, found u32

Some errors occurred: E0277, E0308, E0425, E0609.
Expand Down
2 changes: 1 addition & 1 deletion crates/macro/tests/html-tag-pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use yew_macro::{html, test_html};

test_html! { |t1|
<div>
<div></div>
<div data-key="abc"></div>
<div class="parent">
<span class="child", value="anything",></span>
<input type="text" value="placeholder" />
Expand Down

0 comments on commit 8962b77

Please sign in to comment.