Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend error message #960

Merged
merged 40 commits into from
Mar 5, 2020
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a0b2e96
Initial
captain-yossarian Feb 22, 2020
ad35203
formatting
captain-yossarian Feb 23, 2020
d41d466
wip
captain-yossarian Feb 23, 2020
5ca1685
add util fn
captain-yossarian Feb 23, 2020
0538813
message update
captain-yossarian Feb 23, 2020
166e4b4
refactor
captain-yossarian Feb 23, 2020
1516923
update error logic
captain-yossarian Feb 24, 2020
1cefeae
message refactor
captain-yossarian Feb 24, 2020
a056c89
remove debug flag
captain-yossarian Feb 24, 2020
e0b0684
update
captain-yossarian Feb 24, 2020
bc3b08c
add stderr
captain-yossarian Feb 25, 2020
69f0e0a
update condition
captain-yossarian Feb 26, 2020
8ea567c
CR fix
captain-yossarian Feb 27, 2020
81edf1b
remove empty line
captain-yossarian Feb 27, 2020
ae76a38
update
captain-yossarian Feb 27, 2020
3546d81
update error message
captain-yossarian Feb 27, 2020
b0cef02
wip
captain-yossarian Feb 27, 2020
9c69526
work
captain-yossarian Feb 27, 2020
145393a
unchange clippy
captain-yossarian Feb 27, 2020
6f307f9
update
captain-yossarian Feb 27, 2020
f7681e5
remove logs
captain-yossarian Feb 27, 2020
990437e
println
captain-yossarian Feb 27, 2020
f421174
format
captain-yossarian Feb 27, 2020
23bdf72
clippy
captain-yossarian Feb 27, 2020
62bb054
VERY WIP
captain-yossarian Feb 27, 2020
8fdbc69
error collect
captain-yossarian Feb 28, 2020
bf42a1a
refactor
captain-yossarian Feb 28, 2020
b9b2a6d
add second argument
captain-yossarian Feb 28, 2020
ee82df4
format
captain-yossarian Feb 28, 2020
6ee8610
wip
captain-yossarian Feb 29, 2020
0ff9648
wip with main parsser
captain-yossarian Feb 29, 2020
53bd8ce
wip
captain-yossarian Mar 1, 2020
a619b5b
receive unexpected error
captain-yossarian Mar 1, 2020
8a4f067
Fix with props checks
jstarry Mar 3, 2020
b55d572
merge
captain-yossarian Mar 4, 2020
748c62d
remove comments
captain-yossarian Mar 4, 2020
b8c8a38
update
captain-yossarian Mar 4, 2020
5b64ce8
format
captain-yossarian Mar 5, 2020
114cb16
Merge remote-tracking branch 'origin' into issue/664
captain-yossarian Mar 5, 2020
7fd2382
Fix test
jstarry Mar 5, 2020
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
106 changes: 93 additions & 13 deletions crates/macro/src/html_tree/html_component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use boolinator::Boolinator;
use proc_macro2::Span;
use quote::{quote, quote_spanned, ToTokens};
use std::cmp::Ordering;
use std::collections::HashMap;
use syn::buffer::Cursor;
use syn::parse;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
Expand Down Expand Up @@ -359,6 +360,9 @@ impl Props {
Props::None => None,
}
}
fn collision_message() -> &'static str {
"Using special syntax `with props` along with named prop is not allowed. This rule does not apply to special `ref` prop"
}
}

impl PeekValue<PropType> for Props {
Expand Down Expand Up @@ -388,28 +392,93 @@ struct ListProps {
props: Vec<HtmlProp>,
node_ref: Option<Expr>,
}

impl Parse for ListProps {
fn parse(input: ParseStream) -> ParseResult<Self> {
impl ListProps {
fn collect_props(input: ParseStream) -> ParseResult<Vec<HtmlProp>> {
let mut props: Vec<HtmlProp> = Vec::new();
while HtmlProp::peek(input.cursor()).is_some() {
props.push(input.parse::<HtmlProp>()?);
}
Ok(props)
}

fn remove_refs(mut props: Vec<HtmlProp>) -> ListProps {
let ref_position = props.iter().position(|p| p.label.to_string() == "ref");
let node_ref = ref_position.map(|i| props.remove(i).value);
for prop in &props {
ListProps { props, node_ref }
}

fn apply_edge_cases(props: &Vec<HtmlProp>, cases: &[&str]) -> Result<(), syn::Error> {
let mut map: HashMap<&str, Box<dyn Fn(&HtmlProp) -> Result<_, syn::Error>>> =
HashMap::new();

let ref_handler = |prop: &HtmlProp| -> Result<_, syn::Error> {
if prop.label.to_string() == "ref" {
return Err(syn::Error::new_spanned(&prop.label, "too many refs set"));
Err(syn::Error::new_spanned(&prop.label, "too many refs set"))
} else {
Ok(())
}
};

let type_handler = |prop: &HtmlProp| -> Result<_, syn::Error> {
if prop.label.to_string() == "type" {
return Err(syn::Error::new_spanned(&prop.label, "expected identifier"));
Err(syn::Error::new_spanned(&prop.label, "expected identifier"))
} else {
Ok(())
}
};

let unexpected_handler = |prop: &HtmlProp| -> Result<_, syn::Error> {
if !prop.label.extended.is_empty() {
return Err(syn::Error::new_spanned(&prop.label, "expected identifier"));
Err(syn::Error::new_spanned(&prop.label, "expected identifier"))
} else {
Ok(())
}
};

map.insert("ref", Box::new(ref_handler));
map.insert("type", Box::new(type_handler));
map.insert("unexpected", Box::new(unexpected_handler));

let errors = props.iter().fold(vec![], |acc, prop: &HtmlProp| {
[
acc,
cases
.iter()
.map(|elem| match map.get(elem) {
Some(handler) => handler(prop),
None => Err(syn::Error::new_spanned(&prop.label, "something went wrong")),
})
.filter(Result::is_err)
.collect::<Vec<Result<_, syn::Error>>>(),
]
.concat()
});

for error in errors {
return error;
}

Ok(())
}
}

impl Parse for ListProps {
fn parse(input: ParseStream) -> ParseResult<Self> {
let props = ListProps::collect_props(input)?;

if let Some(ident) = input.cursor().ident() {
if ident.0 == "with" {
return Err(input.error(Props::collision_message()));
}
}

let ListProps {
mut props,
node_ref,
} = ListProps::remove_refs(props);

ListProps::apply_edge_cases(&props, &["ref"])?;

// alphabetize
props.sort_by(|a, b| {
if a.label == b.label {
Expand Down Expand Up @@ -442,16 +511,27 @@ impl Parse for WithProps {
return Err(input.error("expected to find `with` token"));
}
let props = input.parse::<Ident>()?;

let _ = input.parse::<Token![,]>();

// Check for the ref tag after `with`
let mut node_ref = None;
if let Some(ident) = input.cursor().ident() {
let prop = input.parse::<HtmlProp>()?;
if ident.0 == "ref" {
node_ref = Some(prop.value);
} else {
return Err(syn::Error::new_spanned(&prop.label, "unexpected token"));
if input.cursor().ident().is_some() {
let ListProps {
props: list_props,
node_ref: reference,
} = ListProps::remove_refs(ListProps::collect_props(input)?);
node_ref = reference;

for prop in &list_props {
if prop.label.to_string() == "ref" {
return Err(syn::Error::new_spanned(&prop.label, "too many refs set"));
} else {
return Err(syn::Error::new_spanned(
&prop.label,
Props::collision_message(),
));
}
}
}

Expand Down
8 changes: 8 additions & 0 deletions tests/macro/html-component-fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,17 @@ fn compile_fail() {
html! { <Child props /> };
html! { <Child with props > };
html! { <Child with props ref=() ref=() /> };
html! { <Child with props ref=() ref=() value=1 /> };
html! { <Child with props ref=() value=1 ref=() /> };
html! { <Child with props value=1 ref=() ref=() /> };
html! { <Child value=1 with props ref=() ref=() /> };
html! { <Child value=1 ref=() with props ref=() /> };
html! { <Child ref=() ref=() value=1 with props /> };
html! { <Child ref=() with props /> };
html! { <Child with blah /> };
html! { <Child with props () /> };
html! { <Child value=1 with props /> };
html! { <Child with props value=1 /> };
html! { <Child type=0 /> };
html! { <Child invalid-prop-name=0 /> };
html! { <Child unknown="unknown" /> };
Expand Down
Loading