Skip to content

Commit bc46062

Browse files
authored
Refactor html tag peeking (#1738)
* Fix html macro tests * Fix generics with lowercase type param * Satisfy clippy * Parse generics with multiple type params * Reorganize tests * Remove Peek for HtmlTree * Add additional test * cargo fmt * Remove peek_component_type * Remove failing tests
1 parent d1f15b6 commit bc46062

26 files changed

+682
-563
lines changed

packages/yew-macro/src/html_tree/html_component.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -141,29 +141,52 @@ impl HtmlComponent {
141141
Some(cursor)
142142
}
143143

144-
fn path_arguments(cursor: Cursor) -> Option<(PathArguments, Cursor)> {
145-
let (punct, cursor) = cursor.punct()?;
144+
/// Refer to the [`syn::parse::Parse`] implementation for [`AngleBracketedGenericArguments`].
145+
fn path_arguments(mut cursor: Cursor) -> Option<(PathArguments, Cursor)> {
146+
let (punct, c) = cursor.punct()?;
147+
cursor = c;
146148
(punct.as_char() == '<').as_option()?;
147149

148-
let (ty, cursor) = Self::peek_type(cursor)?;
150+
let mut args = Punctuated::new();
149151

150-
let (punct, cursor) = cursor.punct()?;
151-
(punct.as_char() == '>').as_option()?;
152+
loop {
153+
let punct = cursor.punct();
154+
if let Some((punct, c)) = punct {
155+
if punct.as_char() == '>' {
156+
cursor = c;
157+
break;
158+
}
159+
}
160+
161+
let (ty, c) = Self::peek_type(cursor);
162+
cursor = c;
163+
164+
args.push_value(GenericArgument::Type(ty));
165+
166+
let punct = cursor.punct();
167+
if let Some((punct, c)) = punct {
168+
cursor = c;
169+
if punct.as_char() == '>' {
170+
break;
171+
} else if punct.as_char() == ',' {
172+
args.push_punct(Token![,](Span::call_site()))
173+
}
174+
}
175+
}
152176

153177
Some((
154178
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
155179
colon2_token: None,
156180
lt_token: Token![<](Span::call_site()),
157-
args: vec![GenericArgument::Type(ty)].into_iter().collect(),
181+
args,
158182
gt_token: Token![>](Span::call_site()),
159183
}),
160184
cursor,
161185
))
162186
}
163187

164-
fn peek_type(mut cursor: Cursor) -> Option<(Type, Cursor)> {
188+
fn peek_type(mut cursor: Cursor) -> (Type, Cursor) {
165189
let mut colons_optional = true;
166-
let mut last_ident = None;
167190
let mut leading_colon = None;
168191
let mut segments = Punctuated::new();
169192

@@ -180,7 +203,6 @@ impl HtmlComponent {
180203

181204
if let Some((ident, c)) = post_colons_cursor.ident() {
182205
cursor = c;
183-
last_ident = Some(ident.clone());
184206
let arguments = if let Some((args, c)) = Self::path_arguments(cursor) {
185207
cursor = c;
186208
args
@@ -197,11 +219,7 @@ impl HtmlComponent {
197219
colons_optional = false;
198220
}
199221

200-
let type_str = last_ident?.to_string();
201-
type_str.is_ascii().as_option()?;
202-
type_str.bytes().next()?.is_ascii_uppercase().as_option()?;
203-
204-
Some((
222+
(
205223
Type::Path(TypePath {
206224
qself: None,
207225
path: Path {
@@ -210,7 +228,7 @@ impl HtmlComponent {
210228
},
211229
}),
212230
cursor,
213-
))
231+
)
214232
}
215233
}
216234

@@ -233,7 +251,7 @@ impl PeekValue<Type> for HtmlComponentOpen {
233251
fn peek(cursor: Cursor) -> Option<Type> {
234252
let (punct, cursor) = cursor.punct()?;
235253
(punct.as_char() == '<').as_option()?;
236-
let (typ, _) = HtmlComponent::peek_type(cursor)?;
254+
let (typ, _) = HtmlComponent::peek_type(cursor);
237255
Some(typ)
238256
}
239257
}
@@ -267,7 +285,7 @@ impl PeekValue<Type> for HtmlComponentClose {
267285
let (punct, cursor) = cursor.punct()?;
268286
(punct.as_char() == '/').as_option()?;
269287

270-
let (typ, cursor) = HtmlComponent::peek_type(cursor)?;
288+
let (typ, cursor) = HtmlComponent::peek_type(cursor);
271289

272290
let (punct, _) = cursor.punct()?;
273291
(punct.as_char() == '>').as_option()?;

packages/yew-macro/src/html_tree/mod.rs

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use crate::PeekValue;
22
use proc_macro2::{Ident, Span, TokenStream};
33
use quote::{quote, quote_spanned, ToTokens};
4-
use syn::buffer::Cursor;
4+
use syn::ext::IdentExt;
55
use syn::parse::{Parse, ParseStream, Result};
66
use syn::spanned::Spanned;
7+
use syn::Token;
78

89
mod html_block;
910
mod html_component;
@@ -41,7 +42,7 @@ pub enum HtmlTree {
4142

4243
impl Parse for HtmlTree {
4344
fn parse(input: ParseStream) -> Result<Self> {
44-
let html_type = HtmlTree::peek(input.cursor())
45+
let html_type = Self::peek_html_type(input)
4546
.ok_or_else(|| input.error("expected a valid html element"))?;
4647
let html_tree = match html_type {
4748
HtmlType::Empty => HtmlTree::Empty,
@@ -54,18 +55,50 @@ impl Parse for HtmlTree {
5455
}
5556
}
5657

57-
impl PeekValue<HtmlType> for HtmlTree {
58-
fn peek(cursor: Cursor) -> Option<HtmlType> {
59-
if cursor.eof() {
58+
impl HtmlTree {
59+
/// Determine the [`HtmlType`] before actually parsing it.
60+
/// Even though this method accepts a [`ParseStream`], it is forked and the original stream is not modified.
61+
/// Once a certain `HtmlType` can be deduced for certain, the function eagerly returns with the appropriate type.
62+
/// If invalid html tag, returns `None`.
63+
fn peek_html_type(input: ParseStream) -> Option<HtmlType> {
64+
let input = input.fork(); // do not modify original ParseStream
65+
66+
if input.is_empty() {
6067
Some(HtmlType::Empty)
61-
} else if HtmlList::peek(cursor).is_some() {
62-
Some(HtmlType::List)
63-
} else if HtmlComponent::peek(cursor).is_some() {
64-
Some(HtmlType::Component)
65-
} else if HtmlElement::peek(cursor).is_some() {
66-
Some(HtmlType::Element)
67-
} else if HtmlBlock::peek(cursor).is_some() {
68+
} else if input
69+
.cursor()
70+
.group(proc_macro2::Delimiter::Brace)
71+
.is_some()
72+
{
6873
Some(HtmlType::Block)
74+
} else if input.peek(Token![<]) {
75+
let _lt: Token![<] = input.parse().ok()?;
76+
77+
// eat '/' character for unmatched closing tag
78+
let _slash: Option<Token![/]> = input.parse().ok();
79+
80+
if input.peek(Token![>]) {
81+
Some(HtmlType::List)
82+
} else if input.peek(Token![@]) {
83+
Some(HtmlType::Element) // dynamic element
84+
} else if input.peek(Token![::]) {
85+
Some(HtmlType::Component)
86+
} else if input.peek(Ident::peek_any) {
87+
let ident: Ident = input.parse().ok()?;
88+
let ident_str = ident.to_string();
89+
90+
if input.peek(Token![=]) || (input.peek(Token![?]) && input.peek2(Token![=])) {
91+
Some(HtmlType::List)
92+
} else if ident_str.chars().next().unwrap().is_ascii_uppercase()
93+
|| input.peek(Token![::])
94+
{
95+
Some(HtmlType::Component)
96+
} else {
97+
Some(HtmlType::Element)
98+
}
99+
} else {
100+
None
101+
}
69102
} else {
70103
None
71104
}
@@ -94,7 +127,7 @@ pub enum HtmlRoot {
94127

95128
impl Parse for HtmlRoot {
96129
fn parse(input: ParseStream) -> Result<Self> {
97-
let html_root = if HtmlTree::peek(input.cursor()).is_some() {
130+
let html_root = if HtmlTree::peek_html_type(input).is_some() {
98131
Self::Tree(input.parse()?)
99132
} else if HtmlIterable::peek(input.cursor()).is_some() {
100133
Self::Iterable(Box::new(input.parse()?))
@@ -153,7 +186,7 @@ impl ToNodeIterator for HtmlTree {
153186
fn to_node_iterator_stream(&self) -> Option<TokenStream> {
154187
match self {
155188
HtmlTree::Block(block) => block.to_node_iterator_stream(),
156-
// everthing else is just a single node.
189+
// everything else is just a single node.
157190
_ => None,
158191
}
159192
}
File renamed without changes.

packages/yew-macro/tests/html_macro/html-block-fail.stderr renamed to packages/yew-macro/tests/html_macro/block-fail.stderr

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0277]: `()` doesn't implement `std::fmt::Display`
2-
--> $DIR/html-block-fail.rs:6:15
2+
--> $DIR/block-fail.rs:6:15
33
|
44
6 | { () }
55
| ^^ `()` cannot be formatted with the default formatter
@@ -14,7 +14,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
1414
= note: required by `std::convert::Into::into`
1515

1616
error[E0277]: `()` doesn't implement `std::fmt::Display`
17-
--> $DIR/html-block-fail.rs:12:16
17+
--> $DIR/block-fail.rs:12:16
1818
|
1919
12 | <div>{ not_tree() }</div>
2020
| ^^^^^^^^ `()` cannot be formatted with the default formatter
@@ -29,14 +29,14 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
2929
= note: required by `std::convert::Into::into`
3030

3131
error[E0277]: `()` doesn't implement `std::fmt::Display`
32-
--> $DIR/html-block-fail.rs:15:17
32+
--> $DIR/block-fail.rs:15:17
3333
|
3434
15 | <>{ for (0..3).map(|_| not_tree()) }</>
3535
| ^^^^^^ `()` cannot be formatted with the default formatter
3636
|
37-
::: $WORKSPACE/packages/yew/src/utils.rs:51:8
37+
::: $WORKSPACE/packages/yew/src/utils.rs
3838
|
39-
51 | T: Into<R>,
39+
| T: Into<R>,
4040
| ------- required by this bound in `yew::utils::into_node_iter`
4141
|
4242
= help: the trait `std::fmt::Display` is not implemented for `()`
File renamed without changes.

packages/yew-macro/tests/html_macro/html-component-fail.rs renamed to packages/yew-macro/tests/html_macro/component-fail.rs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use std::marker::PhantomData;
21
use yew::html::ChildrenRenderer;
32
use yew::prelude::*;
43

@@ -52,28 +51,6 @@ impl Component for ChildContainer {
5251
}
5352
}
5453

55-
pub struct Generic<G> {
56-
marker: PhantomData<G>,
57-
}
58-
59-
impl Component for Generic<String> {
60-
type Message = ();
61-
type Properties = ();
62-
63-
fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
64-
unimplemented!()
65-
}
66-
fn update(&mut self, _: Self::Message) -> ShouldRender {
67-
unimplemented!()
68-
}
69-
fn change(&mut self, _: Self::Properties) -> ShouldRender {
70-
unimplemented!()
71-
}
72-
fn view(&self) -> Html {
73-
unimplemented!()
74-
}
75-
}
76-
7754
fn compile_fail() {
7855
html! { <Child> };
7956
html! { <Child:: /> };
@@ -130,9 +107,6 @@ fn compile_fail() {
130107
</ChildContainer>
131108
};
132109

133-
html! { <Generic<String>></Generic> };
134-
html! { <Generic<String>></Generic<Vec<String>>> };
135-
136110
html_nested! {
137111
<span>{ 1 }</span>
138112
<span>{ 2 }</span>

0 commit comments

Comments
 (0)