1use super::literal::HotLiteral;
18use crate::{innerlude::*, partial_closure::PartialClosure};
19
20use proc_macro2::TokenStream as TokenStream2;
21use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
22use std::fmt::Display;
23use syn::{
24 ext::IdentExt,
25 parse::{Parse, ParseStream},
26 parse_quote,
27 spanned::Spanned,
28 Block, Expr, ExprBlock, ExprClosure, ExprIf, Ident, Lit, LitBool, LitFloat, LitInt, LitStr,
29 Stmt, Token,
30};
31
32#[derive(PartialEq, Eq, Clone, Debug, Hash)]
36pub struct Attribute {
37 pub name: AttributeName,
41
42 pub colon: Option<Token![:]>,
44
45 pub value: AttributeValue,
49
50 pub comma: Option<Token![,]>,
53
54 pub dyn_idx: DynIdx,
56
57 pub el_name: Option<ElementName>,
60}
61
62impl Parse for Attribute {
63 fn parse(content: ParseStream) -> syn::Result<Self> {
64 if content.peek(Ident::peek_any) && !content.peek2(Token![:]) {
66 let ident = parse_raw_ident(content)?;
67 let comma = content.parse().ok();
68
69 return Ok(Attribute {
70 name: AttributeName::BuiltIn(ident.clone()),
71 colon: None,
72 value: AttributeValue::Shorthand(ident),
73 comma,
74 dyn_idx: DynIdx::default(),
75 el_name: None,
76 });
77 }
78
79 let name = match content.peek(LitStr) {
81 true => AttributeName::Custom(content.parse::<LitStr>()?),
82 false => AttributeName::BuiltIn(parse_raw_ident(content)?),
83 };
84
85 let colon = Some(content.parse::<Token![:]>()?);
87
88 let value = AttributeValue::parse(content)?;
92
93 let comma = content.parse::<Token![,]>().ok();
94
95 let attr = Attribute {
96 name,
97 value,
98 colon,
99 comma,
100 dyn_idx: DynIdx::default(),
101 el_name: None,
102 };
103
104 Ok(attr)
105 }
106}
107
108impl Attribute {
109 pub fn from_raw(name: AttributeName, value: AttributeValue) -> Self {
111 Self {
112 name,
113 colon: Default::default(),
114 value,
115 comma: Default::default(),
116 dyn_idx: Default::default(),
117 el_name: None,
118 }
119 }
120
121 pub fn set_dyn_idx(&self, idx: usize) {
123 self.dyn_idx.set(idx);
124 }
125
126 pub fn get_dyn_idx(&self) -> usize {
128 self.dyn_idx.get()
129 }
130
131 pub fn span(&self) -> proc_macro2::Span {
132 self.name.span()
133 }
134
135 pub fn as_lit(&self) -> Option<&HotLiteral> {
136 match &self.value {
137 AttributeValue::AttrLiteral(lit) => Some(lit),
138 _ => None,
139 }
140 }
141
142 pub fn with_literal(&self, f: impl FnOnce(&HotLiteral)) {
144 if let AttributeValue::AttrLiteral(ifmt) = &self.value {
145 f(ifmt);
146 }
147 }
148
149 pub fn ifmt(&self) -> Option<&IfmtInput> {
150 match &self.value {
151 AttributeValue::AttrLiteral(HotLiteral::Fmted(input)) => Some(input),
152 _ => None,
153 }
154 }
155
156 pub fn as_static_str_literal(&self) -> Option<(&AttributeName, &IfmtInput)> {
157 match &self.value {
158 AttributeValue::AttrLiteral(lit) => match &lit {
159 HotLiteral::Fmted(input) if input.is_static() => Some((&self.name, input)),
160 _ => None,
161 },
162 _ => None,
163 }
164 }
165
166 pub fn is_static_str_literal(&self) -> bool {
167 self.as_static_str_literal().is_some()
168 }
169
170 pub fn rendered_as_dynamic_attr(&self) -> TokenStream2 {
171 if let AttributeName::Spread(_) = self.name {
173 let AttributeValue::AttrExpr(expr) = &self.value else {
174 unreachable!("Spread attributes should always be expressions")
175 };
176 return quote! { {#expr}.into_boxed_slice() };
177 }
178
179 let el_name = self
180 .el_name
181 .as_ref()
182 .expect("el_name rendered as a dynamic attribute should always have an el_name set");
183
184 let ns = |name: &AttributeName| match (el_name, name) {
185 (ElementName::Ident(i), AttributeName::BuiltIn(_)) => {
186 quote! { dioxus_elements::#i::#name.1 }
187 }
188 _ => quote! { None },
189 };
190
191 let volatile = |name: &AttributeName| match (el_name, name) {
192 (ElementName::Ident(i), AttributeName::BuiltIn(_)) => {
193 quote! { dioxus_elements::#i::#name.2 }
194 }
195 _ => quote! { false },
196 };
197
198 let attribute = |name: &AttributeName| match name {
199 AttributeName::BuiltIn(name) => match el_name {
200 ElementName::Ident(_) => quote! { dioxus_elements::#el_name::#name.0 },
201 ElementName::Custom(_) => {
202 let as_string = name.to_string();
203 quote!(#as_string)
204 }
205 },
206 AttributeName::Custom(s) => quote! { #s },
207 AttributeName::Spread(_) => unreachable!("Spread attributes are handled elsewhere"),
208 };
209
210 let attribute = {
211 let value = &self.value;
212 let name = &self.name;
213 let is_not_event = !self.name.is_likely_event();
214
215 match &self.value {
216 AttributeValue::AttrLiteral(_)
217 | AttributeValue::AttrExpr(_)
218 | AttributeValue::Shorthand(_)
219 | AttributeValue::IfExpr { .. }
220 if is_not_event =>
221 {
222 let name = &self.name;
223 let ns = ns(name);
224 let volatile = volatile(name);
225 let attribute = attribute(name);
226 let value = quote! { #value };
227
228 quote! {
229 dioxus_core::Attribute::new(
230 #attribute,
231 #value,
232 #ns,
233 #volatile
234 )
235 }
236 }
237 AttributeValue::EventTokens(_) | AttributeValue::AttrExpr(_) => {
238 let (tokens, span) = match &self.value {
239 AttributeValue::EventTokens(tokens) => {
240 (tokens.to_token_stream(), tokens.span())
241 }
242 AttributeValue::AttrExpr(tokens) => {
243 (tokens.to_token_stream(), tokens.span())
244 }
245 _ => unreachable!(),
246 };
247
248 fn check_tokens_is_closure(tokens: &TokenStream2) -> bool {
249 if syn::parse2::<ExprClosure>(tokens.to_token_stream()).is_ok() {
250 return true;
251 }
252 let Ok(block) = syn::parse2::<ExprBlock>(tokens.to_token_stream()) else {
253 return false;
254 };
255 let mut block = █
256 loop {
257 match block.block.stmts.last() {
258 Some(Stmt::Expr(Expr::Closure(_), _)) => return true,
259 Some(Stmt::Expr(Expr::Block(b), _)) => {
260 block = b;
261 continue;
262 }
263 _ => return false,
264 }
265 }
266 }
267 match &self.name {
268 AttributeName::BuiltIn(name) => {
269 let event_tokens_is_closure = check_tokens_is_closure(&tokens);
270 let function_name =
271 quote_spanned! { span => dioxus_elements::events::#name };
272 let function = if event_tokens_is_closure {
273 quote_spanned! { span => #function_name::call_with_explicit_closure }
275 } else {
276 function_name
277 };
278 quote_spanned! { span =>
279 #function(#tokens)
280 }
281 }
282 AttributeName::Custom(_) => unreachable!("Handled elsewhere in the macro"),
283 AttributeName::Spread(_) => unreachable!("Handled elsewhere in the macro"),
284 }
285 }
286 _ => {
287 quote_spanned! { value.span() => dioxus_elements::events::#name(#value) }
288 }
289 }
290 };
291
292 let completion_hints = self.completion_hints();
293 quote! {
294 Box::new([
295 {
296 #completion_hints
297 #attribute
298 }
299 ])
300 }
301 .to_token_stream()
302 }
303
304 pub fn can_be_shorthand(&self) -> bool {
305 if matches!(self.value, AttributeValue::Shorthand(_)) {
307 return true;
308 }
309
310 if let (AttributeName::BuiltIn(name), AttributeValue::AttrExpr(expr)) =
312 (&self.name, &self.value)
313 {
314 if let Ok(Expr::Path(path)) = expr.as_expr() {
315 if path.path.get_ident() == Some(name) {
316 return true;
317 }
318 }
319 }
320
321 false
322 }
323
324 fn completion_hints(&self) -> TokenStream2 {
327 let Attribute {
328 name,
329 value,
330 comma,
331 el_name,
332 ..
333 } = self;
334
335 if comma.is_some() {
337 return quote! {};
338 }
339
340 let (
345 Some(ElementName::Ident(el)),
346 AttributeName::BuiltIn(name),
347 AttributeValue::Shorthand(_),
348 ) = (&el_name, &name, &value)
349 else {
350 return quote! {};
351 };
352 if name.to_string().starts_with("on") {
354 return quote! {};
355 }
356
357 quote! {
358 {
359 #[allow(dead_code)]
360 #[doc(hidden)]
361 mod __completions {
362 pub use super::dioxus_elements::#el::*;
364 pub use super::dioxus_elements::elements::completions::CompleteWithBraces::*;
366 fn ignore() {
367 #name;
368 }
369 }
370 }
371 }
372 }
373}
374
375#[derive(PartialEq, Eq, Clone, Debug, Hash)]
376pub enum AttributeName {
377 Spread(Token![..]),
378
379 BuiltIn(Ident),
381
382 Custom(LitStr),
387}
388
389impl AttributeName {
390 pub fn is_likely_event(&self) -> bool {
391 matches!(self, Self::BuiltIn(ident) if ident.to_string().starts_with("on"))
392 }
393
394 pub fn is_likely_key(&self) -> bool {
395 matches!(self, Self::BuiltIn(ident) if ident == "key")
396 }
397
398 pub fn span(&self) -> proc_macro2::Span {
399 match self {
400 Self::Custom(lit) => lit.span(),
401 Self::BuiltIn(ident) => ident.span(),
402 Self::Spread(dots) => dots.span(),
403 }
404 }
405}
406
407impl Display for AttributeName {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 match self {
410 Self::Custom(lit) => write!(f, "{}", lit.value()),
411 Self::BuiltIn(ident) => write!(f, "{}", ident),
412 Self::Spread(_) => write!(f, ".."),
413 }
414 }
415}
416
417impl ToTokens for AttributeName {
418 fn to_tokens(&self, tokens: &mut TokenStream2) {
419 match self {
420 Self::Custom(lit) => lit.to_tokens(tokens),
421 Self::BuiltIn(ident) => ident.to_tokens(tokens),
422 Self::Spread(dots) => dots.to_tokens(tokens),
423 }
424 }
425}
426
427#[derive(PartialEq, Eq, Clone, Debug, Hash)]
429pub struct Spread {
430 pub dots: Token![..],
431 pub expr: Expr,
432 pub dyn_idx: DynIdx,
433 pub comma: Option<Token![,]>,
434}
435
436impl Spread {
437 pub fn span(&self) -> proc_macro2::Span {
438 self.dots.span()
439 }
440}
441
442#[derive(PartialEq, Eq, Clone, Debug, Hash)]
443pub enum AttributeValue {
444 Shorthand(Ident),
447
448 AttrLiteral(HotLiteral),
454
455 EventTokens(PartialClosure),
461
462 IfExpr(IfAttributeValue),
469
470 AttrExpr(PartialExpr),
473}
474
475impl Parse for AttributeValue {
476 fn parse(content: ParseStream) -> syn::Result<Self> {
477 if content.peek(Token![if]) {
479 return Ok(Self::IfExpr(content.parse::<IfAttributeValue>()?));
480 }
481
482 if content.peek(Token![move]) || content.peek(Token![|]) {
484 let value = content.parse()?;
485 return Ok(AttributeValue::EventTokens(value));
486 }
487
488 if content.peek(LitStr)
489 || content.peek(LitBool)
490 || content.peek(LitFloat)
491 || content.peek(LitInt)
492 {
493 let fork = content.fork();
494 _ = fork.parse::<Lit>().unwrap();
495
496 if content.peek2(Token![,]) || fork.is_empty() {
497 let value = content.parse()?;
498 return Ok(AttributeValue::AttrLiteral(value));
499 }
500 }
501
502 let value = content.parse::<PartialExpr>()?;
503 Ok(AttributeValue::AttrExpr(value))
504 }
505}
506
507impl ToTokens for AttributeValue {
508 fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
509 match self {
510 Self::Shorthand(ident) => ident.to_tokens(tokens),
511 Self::AttrLiteral(ifmt) => ifmt.to_tokens(tokens),
512 Self::IfExpr(if_expr) => if_expr.to_tokens(tokens),
513 Self::AttrExpr(expr) => expr.to_tokens(tokens),
514 Self::EventTokens(closure) => closure.to_tokens(tokens),
515 }
516 }
517}
518
519impl AttributeValue {
520 pub fn span(&self) -> proc_macro2::Span {
521 match self {
522 Self::Shorthand(ident) => ident.span(),
523 Self::AttrLiteral(ifmt) => ifmt.span(),
524 Self::IfExpr(if_expr) => if_expr.span(),
525 Self::AttrExpr(expr) => expr.span(),
526 Self::EventTokens(closure) => closure.span(),
527 }
528 }
529}
530
531#[derive(PartialEq, Eq, Clone, Debug, Hash)]
533pub struct IfAttributeValue {
534 pub condition: Expr,
535 pub then_value: Box<AttributeValue>,
536 pub else_value: Option<Box<AttributeValue>>,
537}
538
539impl IfAttributeValue {
540 pub(crate) fn quote_as_string(&self, diagnostics: &mut Diagnostics) -> Expr {
542 let mut expression = quote! {};
543 let mut current_if_value = self;
544
545 let mut non_string_diagnostic = |span: proc_macro2::Span| -> Expr {
546 Element::add_merging_non_string_diagnostic(diagnostics, span);
547 parse_quote! { ::std::string::String::new() }
548 };
549
550 loop {
551 let AttributeValue::AttrLiteral(lit) = current_if_value.then_value.as_ref() else {
552 return non_string_diagnostic(current_if_value.span());
553 };
554
555 let HotLiteral::Fmted(HotReloadFormattedSegment {
556 formatted_input: new,
557 ..
558 }) = &lit
559 else {
560 return non_string_diagnostic(current_if_value.span());
561 };
562
563 let condition = ¤t_if_value.condition;
564 expression.extend(quote! {
565 if #condition {
566 #new.to_string()
567 } else
568 });
569 match current_if_value.else_value.as_deref() {
570 Some(AttributeValue::IfExpr(else_value)) => {
572 current_if_value = else_value;
573 }
574 Some(AttributeValue::AttrLiteral(lit)) => {
576 if let HotLiteral::Fmted(new) = &lit {
577 let fmted = &new.formatted_input;
578 expression.extend(quote! { { #fmted.to_string() } });
579 break;
580 } else {
581 return non_string_diagnostic(current_if_value.span());
582 }
583 }
584 None => {
586 expression.extend(quote! { { ::std::string::String::new() } });
587 break;
588 }
589 _ => {
590 return non_string_diagnostic(current_if_value.else_value.span());
591 }
592 }
593 }
594
595 parse_quote! {
596 {
597 #expression
598 }
599 }
600 }
601
602 fn span(&self) -> proc_macro2::Span {
603 self.then_value.span()
604 }
605
606 fn is_terminated(&self) -> bool {
607 match &self.else_value {
608 Some(attribute) => match attribute.as_ref() {
609 AttributeValue::IfExpr(if_expr) => if_expr.is_terminated(),
610 _ => true,
611 },
612 None => false,
613 }
614 }
615
616 fn contains_expression(&self) -> bool {
617 fn attribute_value_contains_expression(expr: &AttributeValue) -> bool {
618 match expr {
619 AttributeValue::IfExpr(if_expr) => if_expr.contains_expression(),
620 AttributeValue::AttrLiteral(_) => false,
621 _ => true,
622 }
623 }
624
625 attribute_value_contains_expression(&self.then_value)
626 || self
627 .else_value
628 .as_deref()
629 .is_some_and(attribute_value_contains_expression)
630 }
631
632 fn parse_attribute_value_from_block(block: &Block) -> syn::Result<Box<AttributeValue>> {
633 let stmts = &block.stmts;
634
635 if stmts.len() != 1 {
636 return Err(syn::Error::new(
637 block.span(),
638 "Expected a single statement in the if block",
639 ));
640 }
641
642 let stmt = &stmts[0];
644
645 match stmt {
647 syn::Stmt::Expr(exp, None) => {
648 let value: Result<HotLiteral, syn::Error> = syn::parse2(quote! { #exp });
650 Ok(match value {
651 Ok(res) => Box::new(AttributeValue::AttrLiteral(res)),
652 Err(_) => Box::new(AttributeValue::AttrExpr(PartialExpr::from_expr(exp))),
653 })
654 }
655 _ => Err(syn::Error::new(stmt.span(), "Expected an expression")),
656 }
657 }
658
659 fn to_tokens_with_terminated(
660 &self,
661 tokens: &mut TokenStream2,
662 terminated: bool,
663 contains_expression: bool,
664 ) {
665 let IfAttributeValue {
666 condition,
667 then_value,
668 else_value,
669 } = self;
670
671 fn quote_attribute_value_string(
675 value: &AttributeValue,
676 contains_expression: bool,
677 ) -> TokenStream2 {
678 if let AttributeValue::AttrLiteral(HotLiteral::Fmted(fmted)) = value {
679 if let Some(str) = fmted.to_static().filter(|_| contains_expression) {
680 quote! {
683 {
684 #[allow(clippy::useless_conversion)]
685 #str.into()
686 }
687 }
688 } else {
689 quote! { #value.to_string() }
690 }
691 } else {
692 value.to_token_stream()
693 }
694 }
695
696 let then_value = quote_attribute_value_string(then_value, contains_expression);
697
698 let then_value = if terminated {
699 quote! { #then_value }
700 }
701 else {
703 quote! { Some(#then_value) }
704 };
705
706 let else_value = match else_value.as_deref() {
707 Some(AttributeValue::IfExpr(else_value)) => {
708 let mut tokens = TokenStream2::new();
709 else_value.to_tokens_with_terminated(&mut tokens, terminated, contains_expression);
710 tokens
711 }
712 Some(other) => {
713 let other = quote_attribute_value_string(other, contains_expression);
714 if terminated {
715 quote! { #other }
716 } else {
717 quote! { Some(#other) }
718 }
719 }
720 None => quote! { None },
721 };
722
723 tokens.append_all(quote! {
724 {
725 if #condition {
726 #then_value
727 } else {
728 #else_value
729 }
730 }
731 });
732 }
733}
734
735impl Parse for IfAttributeValue {
736 fn parse(input: ParseStream) -> syn::Result<Self> {
737 let if_expr = input.parse::<ExprIf>()?;
738
739 let stmts = &if_expr.then_branch.stmts;
740
741 if stmts.len() != 1 {
742 return Err(syn::Error::new(
743 if_expr.then_branch.span(),
744 "Expected a single statement in the if block",
745 ));
746 }
747
748 let then_value = Self::parse_attribute_value_from_block(&if_expr.then_branch)?;
750
751 let else_value = match if_expr.else_branch.as_ref() {
753 Some((_, else_branch)) => {
754 let attribute_value = match else_branch.as_ref() {
756 Expr::Block(block) => Self::parse_attribute_value_from_block(&block.block)?,
758 _ => Box::new(syn::parse2(quote! { #else_branch })?),
760 };
761 Some(attribute_value)
762 }
763 None => None,
764 };
765
766 Ok(Self {
767 condition: *if_expr.cond,
768 then_value,
769 else_value,
770 })
771 }
772}
773
774impl ToTokens for IfAttributeValue {
775 fn to_tokens(&self, tokens: &mut TokenStream2) {
776 let terminated = self.is_terminated();
778 let contains_expression = self.contains_expression();
779 self.to_tokens_with_terminated(tokens, terminated, contains_expression)
780 }
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786 use quote::quote;
787 use syn::parse2;
788
789 #[test]
790 fn parse_attrs() {
791 let _parsed: Attribute = parse2(quote! { name: "value" }).unwrap();
792 let _parsed: Attribute = parse2(quote! { name: value }).unwrap();
793 let _parsed: Attribute = parse2(quote! { name: "value {fmt}" }).unwrap();
794 let _parsed: Attribute = parse2(quote! { name: 123 }).unwrap();
795 let _parsed: Attribute = parse2(quote! { name: false }).unwrap();
796 let _parsed: Attribute = parse2(quote! { "custom": false }).unwrap();
797 let _parsed: Attribute = parse2(quote! { prop: "blah".to_string() }).unwrap();
798
799 let _parsed: Attribute = parse2(quote! { "custom": false, }).unwrap();
801 let _parsed: Attribute = parse2(quote! { name: false, }).unwrap();
802
803 let parsed: Attribute = parse2(quote! { name: if true { "value" } }).unwrap();
805 assert!(matches!(parsed.value, AttributeValue::IfExpr(_)));
806 let parsed: Attribute =
807 parse2(quote! { name: if true { "value" } else { "other" } }).unwrap();
808 assert!(matches!(parsed.value, AttributeValue::IfExpr(_)));
809 let parsed: Attribute =
810 parse2(quote! { name: if true { "value" } else if false { "other" } }).unwrap();
811 assert!(matches!(parsed.value, AttributeValue::IfExpr(_)));
812
813 let _parsed: Attribute = parse2(quote! { name }).unwrap();
815 let _parsed: Attribute = parse2(quote! { name, }).unwrap();
816
817 let parsed: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
819 assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));
820 let parsed: Attribute = parse2(quote! { onclick: |e| { "value" } }).unwrap();
821 assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));
822 let parsed: Attribute = parse2(quote! { onclick: |e| { value. } }).unwrap();
823 assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));
824 let parsed: Attribute = parse2(quote! { onclick: move |e| { value. } }).unwrap();
825 assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));
826 let parsed: Attribute = parse2(quote! { onclick: move |e| value }).unwrap();
827 assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));
828 let parsed: Attribute = parse2(quote! { onclick: |e| value, }).unwrap();
829 assert!(matches!(parsed.value, AttributeValue::EventTokens(_)));
830 }
831
832 #[test]
833 fn merge_attrs() {
834 let _a: Attribute = parse2(quote! { class: "value1" }).unwrap();
835 let _b: Attribute = parse2(quote! { class: "value2" }).unwrap();
836
837 let _b: Attribute = parse2(quote! { class: "value2 {something}" }).unwrap();
838 let _b: Attribute = parse2(quote! { class: if value { "other thing" } }).unwrap();
839 let _b: Attribute = parse2(quote! { class: if value { some_expr } }).unwrap();
840
841 let _b: Attribute = parse2(quote! { class: if value { "some_expr" } }).unwrap();
842 dbg!(_b);
843 }
844
845 #[test]
846 fn static_literals() {
847 let a: Attribute = parse2(quote! { class: "value1" }).unwrap();
848 let b: Attribute = parse2(quote! { class: "value {some}" }).unwrap();
849
850 assert!(a.is_static_str_literal());
851 assert!(!b.is_static_str_literal());
852 }
853
854 #[test]
855 fn partial_eqs() {
856 let a: Attribute = parse2(quote! { class: "value1" }).unwrap();
858 let b: Attribute = parse2(quote! { class: "value1" }).unwrap();
859 assert_eq!(a, b);
860
861 let a: Attribute = parse2(quote! { class: var }).unwrap();
863 let b: Attribute = parse2(quote! { class: var }).unwrap();
864 assert_eq!(a, b);
865
866 let a: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
868 let b: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
869 let c: Attribute = parse2(quote! { onclick: move |e| {} }).unwrap();
870 let d: Attribute = parse2(quote! { onclick: { |e| {} } }).unwrap();
871 assert_eq!(a, b);
872 assert_ne!(a, c);
873 assert_ne!(a, d);
874 }
875
876 #[test]
877 fn call_with_explicit_closure() {
878 let mut a: Attribute = parse2(quote! { onclick: |e| {} }).unwrap();
879 a.el_name = Some(parse_quote!(button));
880 assert!(a
881 .rendered_as_dynamic_attr()
882 .to_string()
883 .contains("call_with_explicit_closure"));
884
885 let mut a: Attribute = parse2(quote! { onclick: { let a = 1; |e| {} } }).unwrap();
886 a.el_name = Some(parse_quote!(button));
887 assert!(a
888 .rendered_as_dynamic_attr()
889 .to_string()
890 .contains("call_with_explicit_closure"));
891
892 let mut a: Attribute = parse2(quote! { onclick: { let b = 2; { |e| { b } } } }).unwrap();
893 a.el_name = Some(parse_quote!(button));
894 assert!(a
895 .rendered_as_dynamic_attr()
896 .to_string()
897 .contains("call_with_explicit_closure"));
898
899 let mut a: Attribute = parse2(quote! { onclick: { let r = |e| { b }; r } }).unwrap();
900 a.el_name = Some(parse_quote!(button));
901 assert!(!a
902 .rendered_as_dynamic_attr()
903 .to_string()
904 .contains("call_with_explicit_closure"));
905 }
906
907 #[test]
910 fn reserved_keywords() {
911 let _a: Attribute = parse2(quote! { for: "class" }).unwrap();
912 let _b: Attribute = parse2(quote! { type: "class" }).unwrap();
913 }
914}