diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index 383cab5b9..50a890bbf 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -19,7 +19,7 @@ use crate::innerlude::*; use proc_macro2::TokenStream as TokenStream2; use proc_macro2_diagnostics::SpanDiagnosticExt; -use quote::{quote, ToTokens, TokenStreamExt}; +use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; use std::{collections::HashSet, vec}; use syn::{ parse::{Parse, ParseStream}, @@ -211,11 +211,16 @@ impl Component { let name = &self.name; let generics = &self.generics; + let inner_scope_span = self + .brace + .as_ref() + .map(|b| b.span.join()) + .unwrap_or(self.name.span()); let mut tokens = if let Some(props) = manual_props.as_ref() { - quote! { let mut __manual_props = #props; } + quote_spanned! { props.span() => let mut __manual_props = #props; } } else { - quote! { fc_to_builder(#name #generics) } + quote_spanned! { self.name.span() => fc_to_builder(#name #generics) } }; tokens.append_all(self.add_fields_to_builder( @@ -224,17 +229,23 @@ impl Component { if !self.children.is_empty() { let children = &self.children; + // If the props don't accept children, attach the error to the first child if manual_props.is_some() { - tokens.append_all(quote! { __manual_props.children = { #children }; }) + tokens.append_all( + quote_spanned! { children.first_root_span() => __manual_props.children = #children; }, + ) } else { - tokens.append_all(quote! { .children( { #children } ) }) + tokens.append_all( + quote_spanned! { children.first_root_span() => .children( #children ) }, + ) } } if manual_props.is_some() { tokens.append_all(quote! { __manual_props }) } else { - tokens.append_all(quote! { .build() }) + // If this does fail to build, point the compiler error at the Prop braces + tokens.append_all(quote_spanned! { inner_scope_span => .build() }) } tokens diff --git a/packages/rsx/src/ifmt.rs b/packages/rsx/src/ifmt.rs index 7f6822838..83a816df5 100644 --- a/packages/rsx/src/ifmt.rs +++ b/packages/rsx/src/ifmt.rs @@ -26,7 +26,11 @@ impl IfmtInput { } pub fn new_litstr(source: LitStr) -> Result { - let segments = IfmtInput::from_raw(&source.value())?; + let segments = IfmtInput::from_raw(&source.value()).map_err(|e| { + // If there is an error creating the formatted string, attribute it to the litstr span + let span = source.span(); + syn::Error::new(span, e) + })?; Ok(Self { segments, source }) } @@ -320,7 +324,7 @@ impl FormattedSegmentType { } else { Err(Error::new( Span::call_site(), - "Expected Ident or Expression", + "Failed to parse formatted segment: Expected Ident or Expression", )) } } @@ -404,4 +408,10 @@ mod tests { Some("body { background: red; }".to_string()) ); } + + #[test] + fn error_spans() { + let input = syn::parse2::(quote! { "body {{ background: red; }" }).unwrap_err(); + assert_eq!(input.span().byte_range(), 0..28); + } } diff --git a/packages/rsx/src/template_body.rs b/packages/rsx/src/template_body.rs index 5b860ecdb..ca44d5e1f 100644 --- a/packages/rsx/src/template_body.rs +++ b/packages/rsx/src/template_body.rs @@ -56,7 +56,7 @@ use self::location::DynIdx; use crate::innerlude::Attribute; use crate::*; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro2_diagnostics::SpanDiagnosticExt; use syn::parse_quote; @@ -386,4 +386,12 @@ impl TemplateBody { ) } } + + /// Get the span of the first root of this template + pub(crate) fn first_root_span(&self) -> Span { + match self.roots.first() { + Some(root) => root.span(), + _ => Span::call_site(), + } + } }