Improve error messages for invalid fragments (#1445)

* remove specific version from "stable" check

* update sterr to new rust stable

* improve html list error messages

* run trybuild tests for 1.45
This commit is contained in:
Simon 2020-07-26 12:34:34 +02:00 committed by GitHub
parent bfc71626d2
commit 4667b33b71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 81 additions and 85 deletions

View File

@ -7,7 +7,6 @@ use proc_macro2::Span;
use quote::{quote, quote_spanned, ToTokens};
use std::cmp::Ordering;
use syn::buffer::Cursor;
use syn::parse;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
@ -275,7 +274,7 @@ impl Parse for HtmlComponentOpen {
// backwards compat
let _ = input.parse::<Token![:]>();
let HtmlPropSuffix { stream, div, gt } = input.parse()?;
let props = parse(stream)?;
let props = syn::parse2(stream)?;
Ok(HtmlComponentOpen {
lt,

View File

@ -1,10 +1,9 @@
use super::HtmlChildrenTree;
use super::{html_dashed_name::HtmlDashedName, HtmlChildrenTree};
use crate::html_tree::{HtmlProp, HtmlPropSuffix};
use crate::PeekValue;
use crate::{Peek, PeekValue};
use boolinator::Boolinator;
use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::spanned::Spanned;
use syn::{Expr, Token};
@ -44,23 +43,22 @@ impl Parse for HtmlList {
}
let open = input.parse::<HtmlListOpen>()?;
if !HtmlList::verify_end(input.cursor()) {
return Err(syn::Error::new_spanned(
open,
"this opening fragment has no corresponding closing fragment",
));
}
let mut children = HtmlChildrenTree::new();
while HtmlListClose::peek(input.cursor()).is_none() {
children.parse_child(input)?;
if input.is_empty() {
return Err(syn::Error::new_spanned(
open,
"this opening fragment has no corresponding closing fragment",
));
}
}
input.parse::<HtmlListClose>()?;
Ok(HtmlList {
children,
key: open.key,
key: open.props.key,
})
}
}
@ -81,32 +79,9 @@ impl ToTokens for HtmlList {
}
}
impl HtmlList {
fn verify_end(mut cursor: Cursor) -> bool {
let mut list_stack_count = 1;
loop {
if HtmlListOpen::peek(cursor).is_some() {
list_stack_count += 1;
} else if HtmlListClose::peek(cursor).is_some() {
list_stack_count -= 1;
if list_stack_count == 0 {
break;
}
}
if let Some((_, next)) = cursor.token_tree() {
cursor = next;
} else {
break;
}
}
list_stack_count == 0
}
}
struct HtmlListOpen {
lt: Token![<],
key: Option<Expr>,
props: HtmlListProps,
gt: Token![>],
}
@ -114,8 +89,10 @@ impl PeekValue<()> for HtmlListOpen {
fn peek(cursor: Cursor) -> Option<()> {
let (punct, cursor) = cursor.punct()?;
(punct.as_char() == '<').as_option()?;
if let Some((ident, _)) = cursor.ident() {
(ident == "key").as_option()
// make sure it's either a property (key=value) or it's immediately closed
if let Some((_, cursor)) = HtmlDashedName::peek(cursor) {
let (punct, _) = cursor.punct()?;
(punct.as_char() == '=').as_option()
} else {
let (punct, _) = cursor.punct()?;
(punct.as_char() == '>').as_option()
@ -126,32 +103,9 @@ impl PeekValue<()> for HtmlListOpen {
impl Parse for HtmlListOpen {
fn parse(input: ParseStream) -> ParseResult<Self> {
let lt = input.parse()?;
if input.cursor().ident().is_some() {
let HtmlPropSuffix { stream, gt, .. } = input.parse()?;
let props = parse::<ParseKey>(stream)?;
Ok(HtmlListOpen {
lt,
key: Some(props.key.value),
gt,
})
} else {
let gt = input.parse()?;
Ok(HtmlListOpen { lt, key: None, gt })
}
}
}
struct ParseKey {
key: HtmlProp,
}
impl Parse for ParseKey {
fn parse(input: ParseStream) -> ParseResult<Self> {
let key = input.parse::<HtmlProp>()?;
if !input.is_empty() {
input.error("Only a single key element is allowed on a <></>");
}
Ok(ParseKey { key })
let HtmlPropSuffix { stream, gt, .. } = input.parse()?;
let props = syn::parse2(stream)?;
Ok(Self { lt, props, gt })
}
}
@ -162,6 +116,33 @@ impl ToTokens for HtmlListOpen {
}
}
struct HtmlListProps {
key: Option<Expr>,
}
impl Parse for HtmlListProps {
fn parse(input: ParseStream) -> ParseResult<Self> {
let key = if input.is_empty() {
None
} else {
let prop: HtmlProp = input.parse()?;
if !input.is_empty() {
return Err(input.error("only a single `key` prop is allowed on a fragment"));
}
if prop.label.to_ascii_lowercase_string() != "key" {
return Err(syn::Error::new_spanned(
prop.label,
"fragments only accept the `key` prop",
));
}
Some(prop.value)
};
Ok(Self { key })
}
}
struct HtmlListClose {
lt: Token![<],
div: Token![/],

View File

@ -1,7 +1,6 @@
use crate::html_tree::HtmlDashedName as HtmlPropLabel;
use crate::{Peek, PeekValue};
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use proc_macro2::{TokenStream, TokenTree};
use syn::buffer::Cursor;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::{Expr, Token};
@ -79,8 +78,7 @@ impl Parse for HtmlPropSuffix {
}
let gt: Token![>] = gt.ok_or_else(|| input.error("missing tag close"))?;
let stream: proc_macro2::TokenStream = trees.into_iter().collect();
let stream = TokenStream::from(stream);
let stream: TokenStream = trees.into_iter().collect();
Ok(HtmlPropSuffix { stream, div, gt })
}

View File

@ -9,7 +9,6 @@ use boolinator::Boolinator;
use proc_macro2::{Delimiter, Span};
use quote::{quote, quote_spanned, ToTokens};
use syn::buffer::Cursor;
use syn::parse;
use syn::parse::{Parse, ParseStream, Result as ParseResult};
use syn::spanned::Spanned;
use syn::{Block, Ident, Token};
@ -375,7 +374,7 @@ impl Parse for HtmlTagOpen {
let lt = input.parse::<Token![<]>()?;
let tag_name = input.parse::<TagName>()?;
let TagSuffix { stream, div, gt } = input.parse()?;
let mut attributes: TagAttributes = parse(stream)?;
let mut attributes: TagAttributes = syn::parse2(stream)?;
match &tag_name {
TagName::Lit(name) => {

View File

@ -57,14 +57,14 @@ impl PeekValue<HtmlType> for HtmlTree {
fn peek(cursor: Cursor) -> Option<HtmlType> {
if cursor.eof() {
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 HtmlTag::peek(cursor).is_some() {
Some(HtmlType::Tag)
} else if HtmlBlock::peek(cursor).is_some() {
Some(HtmlType::Block)
} else if HtmlList::peek(cursor).is_some() {
Some(HtmlType::List)
} else {
None
}

View File

@ -16,7 +16,7 @@ error[E0425]: cannot find value `foo` in this scope
87 | #[prop_or_else(foo)]
| ^^^ not found in this scope
|
help: possible candidates are found in other modules, you can import them into scope
help: consider importing one of these items
|
83 | use crate::t10::foo;
|

View File

@ -1,5 +1,5 @@
#[allow(dead_code)]
#[rustversion::attr(stable(1.44), test)]
#[rustversion::attr(stable(1.45), test)]
fn tests() {
let t = trybuild::TestCases::new();
t.pass("tests/derive_props/pass.rs");

View File

@ -8,8 +8,11 @@ fn compile_fail() {
html! { <><></> };
html! { <></><></> };
html! { <>invalid</> };
html! { <key=></>}
html! { <key="key".to_string()>invalid</key>}
html! { <key=></> };
html! { <key="key".to_string()></key> };
html! { <key="first key" key="second key" /> };
html! { <some_attr="test"></> };
}
fn main() {}

View File

@ -15,10 +15,10 @@ error: this closing fragment has no corresponding opening fragment
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: this opening fragment has no corresponding closing fragment
--> $DIR/html-list-fail.rs:6:13
--> $DIR/html-list-fail.rs:6:15
|
6 | html! { <><> };
| ^^
| ^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
@ -57,15 +57,31 @@ error: expected a valid html element
error: expected an expression following this equals sign
--> $DIR/html-list-fail.rs:11:17
|
11 | html! { <key=></>}
11 | html! { <key=></> };
| ^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: this opening fragment has no corresponding closing fragment
--> $DIR/html-list-fail.rs:12:13
error: this closing tag has no corresponding opening tag
--> $DIR/html-list-fail.rs:12:36
|
12 | html! { <key="key".to_string()>invalid</key>}
| ^^^^^^^^^^^^^^^^^^^^^^^
12 | html! { <key="key".to_string()></key> };
| ^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: only a single `key` prop is allowed on a fragment
--> $DIR/html-list-fail.rs:14:30
|
14 | html! { <key="first key" key="second key" /> };
| ^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: fragments only accept the `key` prop
--> $DIR/html-list-fail.rs:15:14
|
15 | html! { <some_attr="test"></> };
| ^^^^^^^^^
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@ -1,7 +1,7 @@
use yew::html;
#[allow(dead_code)]
#[rustversion::attr(stable(1.44), test)]
#[rustversion::attr(stable(1.45), test)]
fn tests() {
let t = trybuild::TestCases::new();