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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/rsx/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ keywords = ["dom", "ui", "gui", "react"]
proc-macro2 = { workspace = true, features = ["span-locations"] }
proc-macro2-diagnostics = { workspace = true }
quote = { workspace = true }
rustversion.workspace = true
syn = { workspace = true, features = ["full", "extra-traits", "visit", "visit-mut"] }

[features]
Expand Down
26 changes: 26 additions & 0 deletions packages/rsx/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,29 @@ impl ToTokens for Diagnostics {
}
}
}

// TODO: Ideally this would be integrated into the existing diagnostics struct, but currently all fields are public so adding
// new fields would be a breaking change. Diagnostics also doesn't expose the message directly so we can't just modify
// the expansion
pub(crate) mod new_diagnostics {
use proc_macro2_diagnostics::SpanDiagnosticExt;
use std::fmt::Display;

use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote_spanned;

pub(crate) fn warning_diagnostic(span: Span, message: impl Display) -> TokenStream2 {
let note = message.to_string();
// If we are compiling on nightly, use diagnostics directly which supports proper warnings through new span apis
if rustversion::cfg!(nightly) {
return span.warning(note).emit_as_item_tokens();
}
quote_spanned! { span =>
const _: () = {
#[deprecated(note = #note)]
struct Warning;
_ = Warning;
};
}
}
}
8 changes: 8 additions & 0 deletions packages/rsx/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ impl BodyNode {
_ => panic!("Element name not available for this node"),
}
}

pub(crate) fn key(&self) -> Option<&AttributeValue> {
match self {
Self::Element(el) => el.key(),
Self::Component(comp) => comp.get_key(),
_ => None,
}
}
}

#[cfg(test)]
Expand Down
26 changes: 21 additions & 5 deletions packages/rsx/src/template_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ impl ToTokens for TemplateBody {
None => quote! { None },
};

let key_warnings = self.check_for_duplicate_keys();

let roots = node.quote_roots();

// Print paths is easy - just print the paths
Expand Down Expand Up @@ -138,6 +140,8 @@ impl ToTokens for TemplateBody {
dioxus_core::Element::Ok({
#diagnostics

#key_warnings

// Components pull in the dynamic literal pool and template in debug mode, so they need to be defined before dynamic nodes
#[cfg(debug_assertions)]
fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate {
Expand Down Expand Up @@ -273,11 +277,7 @@ impl TemplateBody {
}

pub fn implicit_key(&self) -> Option<&AttributeValue> {
match self.roots.first() {
Some(BodyNode::Element(el)) => el.key(),
Some(BodyNode::Component(comp)) => comp.get_key(),
_ => None,
}
self.roots.first().and_then(BodyNode::key)
}

/// Ensure only one key and that the key is not a static str
Expand All @@ -304,6 +304,22 @@ impl TemplateBody {
}
}

fn check_for_duplicate_keys(&self) -> TokenStream2 {
let mut warnings = TokenStream2::new();

// Make sure there are not multiple keys or keys on nodes other than the first in the block
for root in self.roots.iter().skip(1) {
if let Some(key) = root.key() {
warnings.extend(new_diagnostics::warning_diagnostic(
key.span(),
"Keys are only allowed on the first node in the block.",
));
}
}

warnings
}

pub fn get_dyn_node(&self, path: &[u8]) -> &BodyNode {
let mut node = self.roots.get(path[0] as usize).unwrap();
for idx in path.iter().skip(1) {
Expand Down
Loading