Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/router/src/switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl AppRoute {
/// This type allows us have the best of both worlds.
///
/// IMPORTANT: You *must* specify a `<base>` tag on your webpage in order for this to work!
/// For more information, see the
/// For more information, see the
/// [Mozilla Developer Network docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base)
#[derive(Clone, Debug)]
pub struct PublicUrlSwitch(AppRoute);
Expand Down
52 changes: 35 additions & 17 deletions packages/yew-macro/src/html_tree/html_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,29 +141,52 @@ impl HtmlComponent {
Some(cursor)
}

fn path_arguments(cursor: Cursor) -> Option<(PathArguments, Cursor)> {
let (punct, cursor) = cursor.punct()?;
/// Refer to the [`syn::parse::Parse`] implementation for [`AngleBracketedGenericArguments`].
fn path_arguments(mut cursor: Cursor) -> Option<(PathArguments, Cursor)> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I still really want to get rid of this and the peek_type method. Did you run into an issue here?
Other than that this PR is pretty much done, should we just merge this and revisit this topic another time? I don't want to push you to do things you don't really want to do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Peek implementation depends on peek_type which dependends on path_arguments. I think it would be best if this were merged first and I'll try to work on removing this in another PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alright, sounds like a plan

let (punct, c) = cursor.punct()?;
cursor = c;
(punct.as_char() == '<').as_option()?;

let (ty, cursor) = Self::peek_type(cursor)?;
let mut args = Punctuated::new();

let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '>').as_option()?;
loop {
let punct = cursor.punct();
if let Some((punct, c)) = punct {
if punct.as_char() == '>' {
cursor = c;
break;
}
}

let (ty, c) = Self::peek_type(cursor);
cursor = c;

args.push_value(GenericArgument::Type(ty));

let punct = cursor.punct();
if let Some((punct, c)) = punct {
cursor = c;
if punct.as_char() == '>' {
break;
} else if punct.as_char() == ',' {
args.push_punct(Token![,](Span::call_site()))
}
}
}

Some((
PathArguments::AngleBracketed(AngleBracketedGenericArguments {
colon2_token: None,
lt_token: Token![<](Span::call_site()),
args: vec![GenericArgument::Type(ty)].into_iter().collect(),
args,
gt_token: Token![>](Span::call_site()),
}),
cursor,
))
}

fn peek_type(mut cursor: Cursor) -> Option<(Type, Cursor)> {
fn peek_type(mut cursor: Cursor) -> (Type, Cursor) {
let mut colons_optional = true;
let mut last_ident = None;
let mut leading_colon = None;
let mut segments = Punctuated::new();

Expand All @@ -180,7 +203,6 @@ impl HtmlComponent {

if let Some((ident, c)) = post_colons_cursor.ident() {
cursor = c;
last_ident = Some(ident.clone());
let arguments = if let Some((args, c)) = Self::path_arguments(cursor) {
cursor = c;
args
Expand All @@ -197,11 +219,7 @@ impl HtmlComponent {
colons_optional = false;
}

let type_str = last_ident?.to_string();
type_str.is_ascii().as_option()?;
type_str.bytes().next()?.is_ascii_uppercase().as_option()?;

Some((
(
Type::Path(TypePath {
qself: None,
path: Path {
Expand All @@ -210,7 +228,7 @@ impl HtmlComponent {
},
}),
cursor,
))
)
}
}

Expand All @@ -233,7 +251,7 @@ impl PeekValue<Type> for HtmlComponentOpen {
fn peek(cursor: Cursor) -> Option<Type> {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?;
let (typ, _) = HtmlComponent::peek_type(cursor)?;
let (typ, _) = HtmlComponent::peek_type(cursor);
Some(typ)
}
}
Expand Down Expand Up @@ -267,7 +285,7 @@ impl PeekValue<Type> for HtmlComponentClose {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '/').as_option()?;

let (typ, cursor) = HtmlComponent::peek_type(cursor)?;
let (typ, cursor) = HtmlComponent::peek_type(cursor);

let (punct, _) = cursor.punct()?;
(punct.as_char() == '>').as_option()?;
Expand Down
61 changes: 47 additions & 14 deletions packages/yew-macro/src/html_tree/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use crate::PeekValue;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::ext::IdentExt;
use syn::parse::{Parse, ParseStream, Result};
use syn::spanned::Spanned;
use syn::Token;

mod html_block;
mod html_component;
Expand Down Expand Up @@ -41,7 +42,7 @@ pub enum HtmlTree {

impl Parse for HtmlTree {
fn parse(input: ParseStream) -> Result<Self> {
let html_type = HtmlTree::peek(input.cursor())
let html_type = Self::peek_html_type(input)
.ok_or_else(|| input.error("expected a valid html element"))?;
let html_tree = match html_type {
HtmlType::Empty => HtmlTree::Empty,
Expand All @@ -54,18 +55,50 @@ impl Parse for HtmlTree {
}
}

impl PeekValue<HtmlType> for HtmlTree {
fn peek(cursor: Cursor) -> Option<HtmlType> {
if cursor.eof() {
impl HtmlTree {
/// Determine the [`HtmlType`] before actually parsing it.
/// Even though this method accepts a [`ParseStream`], it is forked and the original stream is not modified.
/// Once a certain `HtmlType` can be deduced for certain, the function eagerly returns with the appropriate type.
/// If invalid html tag, returns `None`.
fn peek_html_type(input: ParseStream) -> Option<HtmlType> {
let input = input.fork(); // do not modify original ParseStream

if input.is_empty() {
Some(HtmlType::Empty)
} else if HtmlList::peek(cursor).is_some() {
Some(HtmlType::List)
} else if HtmlComponent::peek(cursor).is_some() {
Some(HtmlType::Component)
} else if HtmlElement::peek(cursor).is_some() {
Some(HtmlType::Element)
} else if HtmlBlock::peek(cursor).is_some() {
} else if input
.cursor()
.group(proc_macro2::Delimiter::Brace)
.is_some()
{
Some(HtmlType::Block)
} else if input.peek(Token![<]) {
let _lt: Token![<] = input.parse().ok()?;

// eat '/' character for unmatched closing tag
let _slash: Option<Token![/]> = input.parse().ok();

if input.peek(Token![>]) {
Some(HtmlType::List)
} else if input.peek(Token![@]) {
Some(HtmlType::Element) // dynamic element
} else if input.peek(Token![::]) {
Some(HtmlType::Component)
} else if input.peek(Ident::peek_any) {
let ident: Ident = input.parse().ok()?;
let ident_str = ident.to_string();

if input.peek(Token![=]) || (input.peek(Token![?]) && input.peek2(Token![=])) {
Some(HtmlType::List)
} else if ident_str.chars().next().unwrap().is_ascii_uppercase()
|| input.peek(Token![::])
{
Some(HtmlType::Component)
} else {
Some(HtmlType::Element)
}
} else {
None
}
} else {
None
}
Expand Down Expand Up @@ -94,7 +127,7 @@ pub enum HtmlRoot {

impl Parse for HtmlRoot {
fn parse(input: ParseStream) -> Result<Self> {
let html_root = if HtmlTree::peek(input.cursor()).is_some() {
let html_root = if HtmlTree::peek_html_type(input).is_some() {
Self::Tree(input.parse()?)
} else if HtmlIterable::peek(input.cursor()).is_some() {
Self::Iterable(Box::new(input.parse()?))
Expand Down Expand Up @@ -153,7 +186,7 @@ impl ToNodeIterator for HtmlTree {
fn to_node_iterator_stream(&self) -> Option<TokenStream> {
match self {
HtmlTree::Block(block) => block.to_node_iterator_stream(),
// everthing else is just a single node.
// everything else is just a single node.
_ => None,
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/html-block-fail.rs:6:15
--> $DIR/block-fail.rs:6:15
|
6 | { () }
| ^^ `()` cannot be formatted with the default formatter
Expand All @@ -14,7 +14,7 @@ error[E0277]: `()` doesn't implement `std::fmt::Display`
= note: required by `std::convert::Into::into`

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

error[E0277]: `()` doesn't implement `std::fmt::Display`
--> $DIR/html-block-fail.rs:15:17
--> $DIR/block-fail.rs:15:17
|
15 | <>{ for (0..3).map(|_| not_tree()) }</>
| ^^^^^^ `()` cannot be formatted with the default formatter
|
::: $WORKSPACE/packages/yew/src/utils.rs:51:8
::: $WORKSPACE/packages/yew/src/utils.rs
|
51 | T: Into<R>,
| T: Into<R>,
| ------- required by this bound in `yew::utils::into_node_iter`
|
= help: the trait `std::fmt::Display` is not implemented for `()`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::marker::PhantomData;
use yew::html::ChildrenRenderer;
use yew::prelude::*;

Expand Down Expand Up @@ -52,28 +51,6 @@ impl Component for ChildContainer {
}
}

pub struct Generic<G> {
marker: PhantomData<G>,
}

impl Component for Generic<String> {
type Message = ();
type Properties = ();

fn create(_: Self::Properties, _: ComponentLink<Self>) -> Self {
unimplemented!()
}
fn update(&mut self, _: Self::Message) -> ShouldRender {
unimplemented!()
}
fn change(&mut self, _: Self::Properties) -> ShouldRender {
unimplemented!()
}
fn view(&self) -> Html {
unimplemented!()
}
}

fn compile_fail() {
html! { <Child> };
html! { <Child:: /> };
Expand Down Expand Up @@ -130,9 +107,6 @@ fn compile_fail() {
</ChildContainer>
};

html! { <Generic<String>></Generic> };
html! { <Generic<String>></Generic<Vec<String>>> };

html_nested! {
<span>{ 1 }</span>
<span>{ 2 }</span>
Expand Down
Loading