Skip to content

Commit

Permalink
Remove support for element listener magical closures (#782)
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarry authored Dec 8, 2019
1 parent f48bc90 commit 0e8ffcc
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 143 deletions.
5 changes: 4 additions & 1 deletion crates/macro/src/html_tree/html_tag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ impl ToTokens for HtmlTag {
#vtag.node_ref = #node_ref;
}
});
let listeners = listeners.iter().map(|(name, callback)| {
let listeners = listeners.iter().map(|listener| {
let name = &listener.label.name;
let callback = &listener.value;

quote_spanned! {name.span()=> {
::yew::html::#name::Wrapper::new(
<::yew::virtual_dom::vtag::VTag<_> as ::yew::virtual_dom::Transformer<_, _, _>>::transform(
Expand Down
171 changes: 57 additions & 114 deletions crates/macro/src/html_tree/html_tag/tag_attributes.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
use crate::html_tree::HtmlProp as TagAttribute;
use crate::PeekValue;
use lazy_static::lazy_static;
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use std::collections::HashMap;
use std::collections::HashSet;
use std::iter::FromIterator;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::{Expr, ExprClosure, ExprTuple, Ident, Pat};
use syn::{Expr, ExprTuple};

pub struct TagAttributes {
pub attributes: Vec<TagAttribute>,
pub listeners: Vec<(Ident, TokenStream)>,
pub listeners: Vec<TagAttribute>,
pub classes: Option<ClassesForm>,
pub value: Option<Expr>,
pub kind: Option<Expr>,
Expand All @@ -25,75 +24,67 @@ pub enum ClassesForm {
Single(Expr),
}

pub struct TagListener {
name: Ident,
handler: Expr,
event_name: String,
}

lazy_static! {
static ref LISTENER_MAP: HashMap<&'static str, &'static str> = {
let mut m = HashMap::new();
m.insert("onclick", "ClickEvent");
m.insert("ondoubleclick", "DoubleClickEvent");
m.insert("onkeypress", "KeyPressEvent");
m.insert("onkeydown", "KeyDownEvent");
m.insert("onkeyup", "KeyUpEvent");
m.insert("onmousedown", "MouseDownEvent");
m.insert("onmousemove", "MouseMoveEvent");
m.insert("onmouseout", "MouseOutEvent");
m.insert("onmouseenter", "MouseEnterEvent");
m.insert("onmouseleave", "MouseLeaveEvent");
m.insert("onmousewheel", "MouseWheelEvent");
m.insert("onmouseover", "MouseOverEvent");
m.insert("onmouseup", "MouseUpEvent");
m.insert("ontouchcancel", "TouchCancel");
m.insert("ontouchend", "TouchEnd");
m.insert("ontouchenter", "TouchEnter");
m.insert("ontouchmove", "TouchMove");
m.insert("ontouchstart", "TouchStart");
m.insert("ongotpointercapture", "GotPointerCaptureEvent");
m.insert("onlostpointercapture", "LostPointerCaptureEvent");
m.insert("onpointercancel", "PointerCancelEvent");
m.insert("onpointerdown", "PointerDownEvent");
m.insert("onpointerenter", "PointerEnterEvent");
m.insert("onpointerleave", "PointerLeaveEvent");
m.insert("onpointermove", "PointerMoveEvent");
m.insert("onpointerout", "PointerOutEvent");
m.insert("onpointerover", "PointerOverEvent");
m.insert("onpointerup", "PointerUpEvent");
m.insert("onscroll", "ScrollEvent");
m.insert("onblur", "BlurEvent");
m.insert("onfocus", "FocusEvent");
m.insert("onsubmit", "SubmitEvent");
m.insert("oninput", "InputData");
m.insert("onchange", "ChangeData");
m.insert("ondrag", "DragEvent");
m.insert("ondragstart", "DragStartEvent");
m.insert("ondragend", "DragEndEvent");
m.insert("ondragenter", "DragEnterEvent");
m.insert("ondragleave", "DragLeaveEvent");
m.insert("ondragover", "DragOverEvent");
m.insert("ondragexit", "DragExitEvent");
m.insert("ondrop", "DragDropEvent");
m.insert("oncontextmenu", "ContextMenuEvent");
m
static ref LISTENER_SET: HashSet<&'static str> = {
HashSet::from_iter(
vec![
"onclick",
"ondoubleclick",
"onkeypress",
"onkeydown",
"onkeyup",
"onmousedown",
"onmousemove",
"onmouseout",
"onmouseenter",
"onmouseleave",
"onmousewheel",
"onmouseover",
"onmouseup",
"ontouchcancel",
"ontouchend",
"ontouchenter",
"ontouchmove",
"ontouchstart",
"ongotpointercapture",
"onlostpointercapture",
"onpointercancel",
"onpointerdown",
"onpointerenter",
"onpointerleave",
"onpointermove",
"onpointerout",
"onpointerover",
"onpointerup",
"onscroll",
"onblur",
"onfocus",
"onsubmit",
"oninput",
"onchange",
"ondrag",
"ondragstart",
"ondragend",
"ondragenter",
"ondragleave",
"ondragover",
"ondragexit",
"ondrop",
"oncontextmenu",
]
.into_iter(),
)
};
}

impl TagAttributes {
fn drain_listeners(attrs: &mut Vec<TagAttribute>) -> Vec<TagListener> {
fn drain_listeners(attrs: &mut Vec<TagAttribute>) -> Vec<TagAttribute> {
let mut i = 0;
let mut drained = Vec::new();
while i < attrs.len() {
let name_str = attrs[i].label.to_string();
if let Some(event_type) = LISTENER_MAP.get(&name_str.as_str()) {
let TagAttribute { label, value } = attrs.remove(i);
drained.push(TagListener {
name: label.name,
handler: value,
event_name: event_type.to_owned().to_string(),
});
if LISTENER_SET.contains(&name_str.as_str()) {
drained.push(attrs.remove(i));
} else {
i += 1;
}
Expand All @@ -119,54 +110,6 @@ impl TagAttributes {
expr => ClassesForm::Single(expr),
}
}

fn map_listener(listener: TagListener) -> ParseResult<(Ident, TokenStream)> {
let TagListener {
name,
event_name,
handler,
} = listener;

let callback: TokenStream = match handler {
Expr::Closure(closure) => {
let ExprClosure {
inputs,
body,
or1_token,
or2_token,
..
} = closure;

let or_span = quote! {#or1_token#or2_token};
if inputs.len() != 1 {
return Err(syn::Error::new_spanned(
or_span,
"there must be one closure argument",
));
}

let var = match inputs.first().unwrap() {
Pat::Ident(pat) => Ok(pat.into_token_stream()),
Pat::Wild(pat) => Ok(pat.into_token_stream()),
_ => Err(syn::Error::new_spanned(or_span, "invalid closure argument")),
}?;
let callback =
Ident::new(&format!("__yew_{}_callback", name.to_string()), name.span());
let segment = syn::PathSegment {
ident: Ident::new(&event_name, name.span()),
arguments: syn::PathArguments::None,
};

quote_spanned! {name.span()=> {
let #callback = move | #var: ::yew::events::#segment | #body;
#callback
}}
}
callback => callback.into_token_stream(),
};

Ok((name, callback))
}
}

impl Parse for TagAttributes {
Expand All @@ -178,7 +121,7 @@ impl Parse for TagAttributes {

let mut listeners = Vec::new();
for listener in TagAttributes::drain_listeners(&mut attributes) {
listeners.push(TagAttributes::map_listener(listener)?);
listeners.push(listener);
}

// Multiple listener attributes are allowed, but no others
Expand Down
4 changes: 1 addition & 3 deletions tests/macro/html-tag-fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ fn compile_fail() {
html! { <a href=() /> };

html! { <input onclick=1 /> };
html! { <input onclick=|| () /> };
html! { <input onclick=|a, b| () /> };
html! { <input onclick=|a: String| () /> };
html! { <input onclick=Callback::from(|a: String| ()) /> };

html! { <input string=NotToString /> };

Expand Down
39 changes: 15 additions & 24 deletions tests/macro/html-tag-fail.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -102,28 +102,10 @@ error: only one `class` attribute allowed
23 | html! { <div class="first" class="second" /> };
| ^^^^^

error: there must be one closure argument
--> $DIR/html-tag-fail.rs:33:28
|
33 | html! { <input onclick=|| () /> };
| ^^

error: there must be one closure argument
--> $DIR/html-tag-fail.rs:34:28
|
34 | html! { <input onclick=|a, b| () /> };
| ^^^^^^

error: invalid closure argument
--> $DIR/html-tag-fail.rs:35:28
|
35 | html! { <input onclick=|a: String| () /> };
| ^^^^^^^^^^^

error: only one `ref` attribute allowed
--> $DIR/html-tag-fail.rs:40:27
--> $DIR/html-tag-fail.rs:38:27
|
40 | html! { <input ref=() ref=() /> };
38 | html! { <input ref=() ref=() /> };
| ^^^

error[E0308]: mismatched types
Expand Down Expand Up @@ -193,13 +175,22 @@ error[E0308]: mismatched types
= note: expected type `yew::callback::Callback<stdweb::webapi::events::mouse::ClickEvent>`
found type `{integer}`

error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:33:20
|
33 | html! { <input onclick=Callback::from(|a: String| ()) /> };
| ^^^^^^^ expected struct `stdweb::webapi::events::mouse::ClickEvent`, found struct `std::string::String`
|
= note: expected type `yew::callback::Callback<stdweb::webapi::events::mouse::ClickEvent>`
found type `yew::callback::Callback<std::string::String>`

error[E0599]: no method named `to_string` found for type `NotToString` in the current scope
--> $DIR/html-tag-fail.rs:37:27
--> $DIR/html-tag-fail.rs:35:27
|
3 | struct NotToString;
| ------------------- method `to_string` not found for this
...
37 | html! { <input string=NotToString /> };
35 | html! { <input string=NotToString /> };
| ^^^^^^^^^^^ method not found in `NotToString`
|
= note: the method `to_string` exists but the following trait bounds were not satisfied:
Expand All @@ -209,9 +200,9 @@ error[E0599]: no method named `to_string` found for type `NotToString` in the cu
candidate #1: `std::string::ToString`

error[E0308]: mismatched types
--> $DIR/html-tag-fail.rs:39:24
--> $DIR/html-tag-fail.rs:37:24
|
39 | html! { <input ref=() /> };
37 | html! { <input ref=() /> };
| ^^ expected struct `yew::html::NodeRef`, found ()
|
= note: expected type `yew::html::NodeRef`
Expand Down
1 change: 0 additions & 1 deletion tests/macro/html-tag-pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ pass_helper! {
</svg>
<img class=("avatar", "hidden") src="http://pic.com" />
<img class="avatar hidden", />
<button onclick=|e| panic!(e) />
<button onclick=&onclick />
<button onclick=onclick />
<a href="http://google.com" />
Expand Down

0 comments on commit 0e8ffcc

Please sign in to comment.