Add struct field visualization to the editor message hierarchy tree visualization on the website (#2917)

* Fix Message Tree: Enforce Structure and Visibility

* Code review

* fix the erroreous ouputs

* error handling for MessageHandler

* Fix website visualization HTML generation

* error handling for tuple-style message enum variant

* cleanup

* Update messages

* Normalize BroadcastEvent

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Mohd Mohsin
2025-08-19 09:34:29 +05:30
committed by GitHub
parent 5ed45ead6f
commit 17d70dc60e
59 changed files with 988 additions and 506 deletions

View File

@@ -31,23 +31,23 @@ pub fn derive_extract_field_impl(input: TokenStream) -> syn::Result<TokenStream>
})
.collect::<Vec<_>>();
let field_str = field_info.into_iter().map(|(name, ty)| (format!("{}: {}", name, ty)));
let field_str = field_info.into_iter().map(|(name, ty)| (format!("{name}: {ty}")));
let res = quote! {
impl #impl_generics #struct_name #ty_generics #where_clause {
pub fn field_types() -> Vec<(String, usize)> {
impl #impl_generics ExtractField for #struct_name #ty_generics #where_clause {
fn field_types() -> Vec<(String, usize)> {
vec![
#((String::from(#field_str), #field_line)),*
]
}
pub fn print_field_types() {
fn print_field_types() {
for (field, line) in Self::field_types() {
println!("{} at line {}", field, line);
}
}
pub fn path() -> &'static str {
fn path() -> &'static str {
file!()
}
}

View File

@@ -1,3 +1,4 @@
use crate::helpers::clean_rust_type_syntax;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, quote};
use syn::{Data, DeriveInput, Fields, Type, parse2};
@@ -11,51 +12,89 @@ pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result<TokenStream
_ => return Err(syn::Error::new(Span::call_site(), "Tried to derive HierarchicalTree for non-enum")),
};
let build_message_tree = data.variants.iter().map(|variant| {
let variant_type = &variant.ident;
let build_message_tree: Result<Vec<_>, syn::Error> = data
.variants
.iter()
.map(|variant| {
let variant_type = &variant.ident;
let has_child = variant
.attrs
.iter()
.any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child"));
let has_child = variant
.attrs
.iter()
.any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child"));
if has_child {
if let Fields::Unnamed(fields) = &variant.fields {
let field_type = &fields.unnamed.first().unwrap().ty;
quote! {
{
let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));
let field_name = stringify!(#field_type);
const message_string: &str = "Message";
if message_string == &field_name[field_name.len().saturating_sub(message_string.len())..] {
// The field is a Message type, recursively build its tree
let sub_tree = #field_type::build_message_tree();
variant_tree.add_variant(sub_tree);
}
message_tree.add_variant(variant_tree);
match &variant.fields {
Fields::Unit => Ok(quote! {
message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
}),
Fields::Unnamed(fields) => {
if has_child {
let field_type = &fields.unnamed.first().unwrap().ty;
Ok(quote! {
{
let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));
let field_name = stringify!(#field_type);
const MESSAGE_SUFFIX: &str = "Message";
if MESSAGE_SUFFIX == &field_name[field_name.len().saturating_sub(MESSAGE_SUFFIX.len())..] {
// The field is a Message type, recursively build its tree
let sub_tree = #field_type::build_message_tree();
variant_tree.add_variant(sub_tree);
} else {
variant_tree.add_fields(vec![format!("{field_name}")]);
}
message_tree.add_variant(variant_tree);
}
})
} else {
let error_msg = match fields.unnamed.len() {
0 => format!("Remove the unnecessary `()` from the `{}` message enum variant.", variant_type),
1 => {
let field_type = &fields.unnamed.first().unwrap().ty;
format!(
"The `{}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\n\
Replace `{}` with a named field using {{curly braces}} instead of a positional field using (parentheses).",
variant_type,
field_type.to_token_stream()
)
}
_ => {
let field_types = fields.unnamed.iter().map(|f| f.ty.to_token_stream().to_string()).collect::<Vec<_>>().join(", ");
format!(
"The `{}` message should be defined as a struct-style (not tuple-style) enum variant to maintain consistent formatting across all editor messages.\n\
Replace `{}` with named fields using {{curly braces}} instead of positional fields using (parentheses).",
variant_type, field_types
)
}
};
Err(syn::Error::new(Span::call_site(), error_msg))
}
}
} else {
quote! {
message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
Fields::Named(fields) => {
let names = fields.named.iter().map(|f| f.ident.as_ref().unwrap());
let ty = fields.named.iter().map(|f| clean_rust_type_syntax(f.ty.to_token_stream().to_string()));
Ok(quote! {
{
let mut field_names = Vec::new();
#(field_names.push(format!("{}: {}",stringify!(#names), #ty));)*
let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));
variant_tree.add_fields(field_names);
message_tree.add_variant(variant_tree);
}
})
}
}
} else {
quote! {
message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
}
}
});
})
.collect();
let build_message_tree = build_message_tree?;
let res = quote! {
impl HierarchicalTree for #input_type {
fn build_message_tree() -> DebugMessageTree {
let mut message_tree = DebugMessageTree::new(stringify!(#input_type));
#(#build_message_tree)*
let message_handler_str = #input_type::message_handler_str();
if message_handler_str.fields().len() > 0 {
message_tree.add_message_handler_field(message_handler_str);
}
message_tree.add_message_handler_field(message_handler_str);
let message_handler_data_str = #input_type::message_handler_data_str();
if message_handler_data_str.fields().len() > 0 {

View File

@@ -43,10 +43,8 @@ pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream
quote! {
#input_item
impl #message_type {
pub fn message_handler_data_str() -> MessageData
{
pub fn message_handler_data_str() -> MessageData {
MessageData::new(format!("{}", stringify!(#type_name)), #type_name::field_types(), #type_name::path())
}
pub fn message_handler_str() -> MessageData {
MessageData::new(format!("{}", stringify!(#input_type)), #input_type::field_types(), #input_type::path())

View File

@@ -110,7 +110,7 @@ pub fn derive_widget_builder_impl(input_item: TokenStream2) -> syn::Result<Token
// Construct the `widget_holder` function
quote::quote! {
#[doc = #widget_holder_doc_comment]
pub fn widget_holder(self) -> crate::messages::layout::utility_types::layout_widget::WidgetHolder{
pub fn widget_holder(self) -> crate::messages::layout::utility_types::layout_widget::WidgetHolder {
crate::messages::layout::utility_types::layout_widget::WidgetHolder::new( crate::messages::layout::utility_types::layout_widget::Widget::#struct_name_ident(self))
}
}