Skip to content
Open
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
35 changes: 35 additions & 0 deletions packages/rsx-hotreload/tests/hotreload_pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,3 +1301,38 @@ fn valid_spread() {

assert!(valid);
}

// JSX-style syntax should hot reload, including when mixed with the regular syntax
#[test]
fn valid_jsx_syntax() {
// Literal changes in JSX-style rsx are hot reloadable
let valid = can_hotreload(
quote! {
<div class="container">
<h1>"Hello, world!"</h1>
</div>
},
quote! {
<div class="new-class">
<h1>"Goodbye, world!"</h1>
</div>
},
);
assert!(valid);

// Rewriting between the two syntax styles is hot reloadable since they
// produce the same templates
let valid = can_hotreload(
quote! {
div { class: "container",
h1 { "Hello, world!" }
}
},
quote! {
<div class="container">
<h1>"Hello, world!"</h1>
</div>
},
);
assert!(valid);
}
47 changes: 34 additions & 13 deletions packages/rsx/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,23 +56,15 @@ impl Parse for Component {
diagnostics,
} = input.parse::<RsxBlock>()?;

let mut component = Self {
children: TemplateBody::new(children),
Ok(Self::from_parts(
name,
generics,
fields,
brace: Some(brace),
spreads,
children,
Some(brace),
diagnostics,
};

// We've received a valid rsx block, but it's not necessarily a valid component
// validating it will dump diagnostics into the output
component.validate_component_path();
component.validate_fields();
component.validate_component_spread();

Ok(component)
))
}
}

Expand Down Expand Up @@ -121,6 +113,35 @@ impl Component {
}
}

/// Assemble a component from its parsed parts, running validation on the result
pub(crate) fn from_parts(
name: syn::Path,
generics: Option<AngleBracketedGenericArguments>,
fields: Vec<Attribute>,
spreads: Vec<Spread>,
children: Vec<BodyNode>,
brace: Option<token::Brace>,
diagnostics: Diagnostics,
) -> Self {
let mut component = Self {
children: TemplateBody::new(children),
name,
generics,
fields,
brace,
spreads,
diagnostics,
};

// We've received a valid rsx block, but it's not necessarily a valid component
// validating it will dump diagnostics into the output
component.validate_component_path();
component.validate_fields();
component.validate_component_spread();

component
}

// Make sure this a proper component path (uppercase ident, a path, or contains an underscorea)
// This should be validated by the RsxBlock parser when it peeks bodynodes
fn validate_component_path(&mut self) {
Expand Down Expand Up @@ -378,7 +399,7 @@ impl Component {
/// Normalize the generics of a path
///
/// Ensure there's a `::` after the last segment if there are generics
fn normalize_path(name: &mut syn::Path) -> Option<AngleBracketedGenericArguments> {
pub(crate) fn normalize_path(name: &mut syn::Path) -> Option<AngleBracketedGenericArguments> {
let seg = name.segments.last_mut()?;

let mut generics = match seg.arguments.clone() {
Expand Down
71 changes: 45 additions & 26 deletions packages/rsx/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,57 @@ impl Parse for Element {
),
}

Ok(Self::from_parts(
name,
block.attributes,
block.spreads,
block.children,
brace,
block.diagnostics,
))
}
}

impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let builder = self.view_builder_pieces();
let definitions = builder.definitions();
let view = builder.view_expr();
let diagnostics = &self.diagnostics;

tokens.append_all(quote! {
{
#diagnostics
#(#definitions)*
#view
}
});
}
}

impl Element {
/// Assemble an element from its parsed parts, merging attributes and spreads
pub(crate) fn from_parts(
name: ElementName,
mut attributes: Vec<Attribute>,
spreads: Vec<Spread>,
children: Vec<BodyNode>,
brace: Option<Brace>,
diagnostics: Diagnostics,
) -> Self {
// Make sure these attributes have element context for name and namespace resolution.
for attr in block.attributes.iter_mut() {
for attr in attributes.iter_mut() {
attr.el_name = Some(name.clone());
}

// Assemble the new element from the contents of the block
let mut element = Element {
brace,
name: name.clone(),
raw_attributes: block.attributes,
children: block.children,
diagnostics: block.diagnostics,
spreads: block.spreads.clone(),
raw_attributes: attributes,
children,
diagnostics,
spreads: spreads.clone(),
merged_attributes: Vec::new(),
};

Expand All @@ -90,7 +128,7 @@ impl Parse for Element {
// And then merge the spreads *after* the attributes are merged. This ensures walking the
// merged attributes in path order stops before we hit the spreads, but spreads are still
// counted as dynamic attributes
for spread in block.spreads.iter() {
for spread in spreads.iter() {
element.merged_attributes.push(Attribute {
name: AttributeName::Spread(spread.dots),
colon: None,
Expand All @@ -100,28 +138,9 @@ impl Parse for Element {
});
}

Ok(element)
}
}

impl ToTokens for Element {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let builder = self.view_builder_pieces();
let definitions = builder.definitions();
let view = builder.view_expr();
let diagnostics = &self.diagnostics;

tokens.append_all(quote! {
{
#diagnostics
#(#definitions)*
#view
}
});
element
}
}

impl Element {
pub(crate) fn add_merging_non_string_diagnostic(diagnostics: &mut Diagnostics, span: Span) {
diagnostics.push(span.error("Cannot merge non-fmt literals").help(
"Only formatted strings can be merged together. If you want to merge literals, you can use a format string.",
Expand Down
Loading
Loading