mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
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:
parent
bfc71626d2
commit
4667b33b71
@ -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,
|
||||
|
||||
@ -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![/],
|
||||
|
||||
@ -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 })
|
||||
}
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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() {}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user