480 lines
16 KiB
Rust
480 lines
16 KiB
Rust
use std::mem;
|
|
|
|
use proc_macro2::TokenStream;
|
|
use quote::quote;
|
|
use syn::visit::{self, Visit};
|
|
use syn::visit_mut::{self, VisitMut};
|
|
use syn::LocalInit;
|
|
|
|
use crate::additional_fields::AdditionalFields;
|
|
use crate::menu::Menus;
|
|
use crate::util;
|
|
use crate::widgets::ViewWidgets;
|
|
|
|
#[derive(Debug)]
|
|
pub(super) struct ComponentVisitor<'errors> {
|
|
pub(super) view_widgets: Option<syn::Result<ViewWidgets>>,
|
|
pub(super) widgets_ty: Option<syn::Type>,
|
|
pub(super) root_name: Option<syn::Ident>,
|
|
pub(super) model_name: Option<syn::Ident>,
|
|
pub(super) sender_name: Option<syn::Ident>,
|
|
pub(super) additional_fields: Option<AdditionalFields>,
|
|
pub(super) menus: Option<Menus>,
|
|
pub(super) errors: &'errors mut Vec<syn::Error>,
|
|
}
|
|
|
|
impl<'errors> ComponentVisitor<'errors> {
|
|
pub(super) fn new(errors: &'errors mut Vec<syn::Error>) -> Self {
|
|
ComponentVisitor {
|
|
view_widgets: None,
|
|
widgets_ty: None,
|
|
root_name: None,
|
|
model_name: None,
|
|
sender_name: None,
|
|
additional_fields: None,
|
|
menus: None,
|
|
errors,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl VisitMut for ComponentVisitor<'_> {
|
|
fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
|
|
let mut remove = false;
|
|
|
|
match item {
|
|
syn::ImplItem::Macro(mac) => {
|
|
match mac.mac.path.get_ident().map(ToString::to_string).as_deref() {
|
|
Some("view") => {
|
|
if self.view_widgets.is_some() {
|
|
self.errors
|
|
.push(syn::Error::new_spanned(&mac, "duplicate view macro"));
|
|
}
|
|
|
|
self.view_widgets.replace(mac.mac.parse_body());
|
|
|
|
remove = true;
|
|
}
|
|
Some("additional_fields") => {
|
|
match mac.mac.parse_body::<AdditionalFields>() {
|
|
Ok(fields) => {
|
|
let existing = self.additional_fields.replace(fields);
|
|
|
|
if existing.is_some() {
|
|
self.errors.push(syn::Error::new_spanned(
|
|
mac,
|
|
"duplicate additional_fields macro",
|
|
));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
self.errors.push(e);
|
|
}
|
|
};
|
|
|
|
remove = true;
|
|
}
|
|
Some("menu") => {
|
|
match mac.mac.parse_body::<Menus>() {
|
|
Ok(menu) => {
|
|
let existing = self.menus.replace(menu);
|
|
|
|
if existing.is_some() {
|
|
self.errors
|
|
.push(syn::Error::new_spanned(mac, "duplicate menu macro"));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
self.errors.push(e);
|
|
}
|
|
};
|
|
remove = true;
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
syn::ImplItem::Fn(func) => {
|
|
if &*func.sig.ident.to_string() == "init" {
|
|
let mut init_fn_visitor = InitFnVisitor::default();
|
|
init_fn_visitor.visit_impl_item_fn(func);
|
|
|
|
self.model_name = init_fn_visitor.model_name;
|
|
self.sender_name = init_fn_visitor.sender_name;
|
|
self.root_name = init_fn_visitor.root_name;
|
|
self.errors.append(&mut init_fn_visitor.errors);
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
if remove {
|
|
*item = null_item();
|
|
}
|
|
|
|
visit_mut::visit_impl_item_mut(self, item);
|
|
}
|
|
|
|
fn visit_impl_item_type_mut(&mut self, ty: &mut syn::ImplItemType) {
|
|
if ty.ident == "Widgets" {
|
|
self.widgets_ty = Some(ty.ty.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(super) struct FactoryComponentVisitor<'errors> {
|
|
pub(super) view_widgets: Option<syn::Result<ViewWidgets>>,
|
|
pub(super) widgets_ty: Option<syn::Type>,
|
|
pub(super) index_ty: Option<syn::Type>,
|
|
pub(super) init_widgets: Option<syn::ImplItemFn>,
|
|
pub(super) root_name: Option<syn::Ident>,
|
|
pub(super) additional_fields: Option<AdditionalFields>,
|
|
pub(super) menus: Option<Menus>,
|
|
pub(super) errors: &'errors mut Vec<syn::Error>,
|
|
}
|
|
|
|
impl<'errors> FactoryComponentVisitor<'errors> {
|
|
pub(super) fn new(errors: &'errors mut Vec<syn::Error>) -> Self {
|
|
FactoryComponentVisitor {
|
|
view_widgets: None,
|
|
widgets_ty: None,
|
|
index_ty: None,
|
|
init_widgets: None,
|
|
root_name: None,
|
|
additional_fields: None,
|
|
menus: None,
|
|
errors,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl VisitMut for FactoryComponentVisitor<'_> {
|
|
fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
|
|
let mut remove = false;
|
|
|
|
match item {
|
|
syn::ImplItem::Macro(mac) => {
|
|
match mac.mac.path.get_ident().map(ToString::to_string).as_deref() {
|
|
Some("view") => {
|
|
if self.view_widgets.is_some() {
|
|
self.errors
|
|
.push(syn::Error::new_spanned(&mac, "duplicate view macro"));
|
|
}
|
|
|
|
self.view_widgets.replace(mac.mac.parse_body());
|
|
|
|
remove = true;
|
|
}
|
|
Some("additional_fields") => {
|
|
match mac.mac.parse_body::<AdditionalFields>() {
|
|
Ok(fields) => {
|
|
let existing = self.additional_fields.replace(fields);
|
|
|
|
if existing.is_some() {
|
|
self.errors.push(syn::Error::new_spanned(
|
|
mac,
|
|
"duplicate additional_fields macro",
|
|
));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
self.errors.push(e);
|
|
}
|
|
};
|
|
|
|
remove = true;
|
|
}
|
|
Some("menu") => {
|
|
match mac.mac.parse_body::<Menus>() {
|
|
Ok(menu) => {
|
|
let existing = self.menus.replace(menu);
|
|
|
|
if existing.is_some() {
|
|
self.errors
|
|
.push(syn::Error::new_spanned(mac, "duplicate menu macro"));
|
|
}
|
|
}
|
|
Err(e) => {
|
|
self.errors.push(e);
|
|
}
|
|
};
|
|
remove = true;
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
syn::ImplItem::Fn(func) => {
|
|
if &*func.sig.ident.to_string() == "init_widgets" {
|
|
let mut init_fn_visitor = InitWidgetsFnVisitor::default();
|
|
init_fn_visitor.visit_impl_item_fn(func);
|
|
|
|
self.root_name = init_fn_visitor.root_name;
|
|
self.errors.append(&mut init_fn_visitor.errors);
|
|
|
|
let existing = self.init_widgets.replace(func.clone());
|
|
if existing.is_some() {
|
|
self.errors.push(syn::Error::new_spanned(
|
|
func,
|
|
"duplicate init_widgets function",
|
|
));
|
|
}
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
if remove {
|
|
*item = null_item();
|
|
}
|
|
|
|
visit_mut::visit_impl_item_mut(self, item);
|
|
}
|
|
|
|
fn visit_impl_item_type_mut(&mut self, ty: &mut syn::ImplItemType) {
|
|
if ty.ident == "Widgets" {
|
|
self.widgets_ty = Some(ty.ty.clone());
|
|
} else if ty.ident == "Root" {
|
|
self.errors.push(syn::Error::new_spanned(
|
|
ty,
|
|
"`Root` type is defined by `view!` macro",
|
|
));
|
|
} else if ty.ident == "Index" {
|
|
self.index_ty = Some(ty.ty.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct InitFnVisitor {
|
|
root_name: Option<syn::Ident>,
|
|
model_name: Option<syn::Ident>,
|
|
sender_name: Option<syn::Ident>,
|
|
errors: Vec<syn::Error>,
|
|
}
|
|
|
|
impl<'ast> Visit<'ast> for InitFnVisitor {
|
|
fn visit_impl_item_fn(&mut self, func: &'ast syn::ImplItemFn) {
|
|
let Some(root_arg) = func.sig.inputs.iter().nth(1) else {
|
|
return;
|
|
};
|
|
let root_name = util::extract_arg_ident(root_arg);
|
|
|
|
match root_name {
|
|
Ok(root_name) => self.root_name = Some(root_name.clone()),
|
|
Err(e) => self.errors.push(e),
|
|
}
|
|
|
|
let Some(sender_arg) = func.sig.inputs.iter().nth(2) else {
|
|
return;
|
|
};
|
|
let sender_name = util::extract_arg_ident(sender_arg);
|
|
|
|
match sender_name {
|
|
Ok(sender_name) => self.sender_name = Some(sender_name.clone()),
|
|
Err(e) => self.errors.push(e),
|
|
}
|
|
|
|
visit::visit_impl_item_fn(self, func);
|
|
}
|
|
|
|
fn visit_expr_struct(&mut self, expr_struct: &'ast syn::ExprStruct) {
|
|
let ident = &expr_struct.path.segments.last().unwrap().ident;
|
|
if ident == "ComponentParts" || ident == "AsyncComponentParts" {
|
|
for field in &expr_struct.fields {
|
|
let member_name = match &field.member {
|
|
syn::Member::Named(ident) => Some(ident.to_string()),
|
|
syn::Member::Unnamed(_) => None,
|
|
};
|
|
|
|
if member_name.as_deref() == Some("model") {
|
|
let model_name = match &field.expr {
|
|
syn::Expr::Path(path) => {
|
|
if let Some(ident) = path.path.get_ident() {
|
|
Ok(ident.clone())
|
|
} else {
|
|
Err(syn::Error::new_spanned(
|
|
path,
|
|
"unable to determine model name",
|
|
))
|
|
}
|
|
}
|
|
_ => Err(syn::Error::new_spanned(
|
|
&field.expr,
|
|
"unable to determine model name",
|
|
)),
|
|
};
|
|
|
|
match model_name {
|
|
Ok(model_name) => self.model_name = Some(model_name),
|
|
Err(e) => self.errors.push(e),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
visit::visit_expr_struct(self, expr_struct);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Default)]
|
|
struct InitWidgetsFnVisitor {
|
|
root_name: Option<syn::Ident>,
|
|
errors: Vec<syn::Error>,
|
|
}
|
|
|
|
impl<'ast> Visit<'ast> for InitWidgetsFnVisitor {
|
|
fn visit_impl_item_fn(&mut self, func: &'ast syn::ImplItemFn) {
|
|
let Some(root_arg) = func.sig.inputs.iter().nth(2) else {
|
|
return;
|
|
};
|
|
let root_name = util::extract_arg_ident(root_arg);
|
|
|
|
match root_name {
|
|
Ok(root_name) => self.root_name = Some(root_name.clone()),
|
|
Err(e) => self.errors.push(e),
|
|
}
|
|
|
|
visit::visit_impl_item_fn(self, func);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(super) struct PreAndPostView<'errors> {
|
|
pub(super) pre_view: Vec<syn::Stmt>,
|
|
pub(super) post_view: Vec<syn::Stmt>,
|
|
errors: &'errors mut Vec<syn::Error>,
|
|
}
|
|
|
|
impl<'errors> PreAndPostView<'errors> {
|
|
pub(super) fn extract(impl_: &mut syn::ItemImpl, errors: &'errors mut Vec<syn::Error>) -> Self {
|
|
let mut visitor = PreAndPostView {
|
|
pre_view: vec![],
|
|
post_view: vec![],
|
|
errors,
|
|
};
|
|
|
|
visitor.visit_item_impl_mut(impl_);
|
|
|
|
visitor
|
|
}
|
|
}
|
|
|
|
impl VisitMut for PreAndPostView<'_> {
|
|
fn visit_impl_item_mut(&mut self, item: &mut syn::ImplItem) {
|
|
if let syn::ImplItem::Fn(func) = item {
|
|
match &*func.sig.ident.to_string() {
|
|
"pre_view" => {
|
|
if !self.pre_view.is_empty() {
|
|
self.errors.push(syn::Error::new_spanned(
|
|
&func,
|
|
"duplicate pre_view function",
|
|
));
|
|
}
|
|
|
|
self.pre_view = func.block.stmts.clone();
|
|
*item = null_item();
|
|
}
|
|
"post_view" => {
|
|
if !self.post_view.is_empty() {
|
|
self.errors.push(syn::Error::new_spanned(
|
|
&func,
|
|
"duplicate post_view function",
|
|
));
|
|
}
|
|
|
|
self.post_view = func.block.stmts.clone();
|
|
*item = null_item();
|
|
}
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
visit_mut::visit_impl_item_mut(self, item);
|
|
}
|
|
}
|
|
|
|
/// Expands the `view_output!` macro expression in the `init` function.
|
|
pub(crate) struct ViewOutputExpander<'errors> {
|
|
/// Whether a `view_output!` macro expression has been successfully expanded.
|
|
expanded: bool,
|
|
|
|
/// View initialization code to inject before the view output.
|
|
view_code: TokenStream,
|
|
|
|
/// Widgets struct initialization.
|
|
widgets_init: Box<syn::Expr>,
|
|
|
|
errors: &'errors mut Vec<syn::Error>,
|
|
}
|
|
|
|
impl ViewOutputExpander<'_> {
|
|
pub(crate) fn expand(
|
|
item_impl: &mut syn::ItemImpl,
|
|
view_code: TokenStream,
|
|
widgets_init: Box<syn::Expr>,
|
|
errors: &mut Vec<syn::Error>,
|
|
) {
|
|
let mut expander = ViewOutputExpander {
|
|
expanded: false,
|
|
view_code,
|
|
widgets_init,
|
|
errors,
|
|
};
|
|
|
|
expander.visit_item_impl_mut(item_impl);
|
|
}
|
|
}
|
|
|
|
impl VisitMut for ViewOutputExpander<'_> {
|
|
fn visit_impl_item_fn_mut(&mut self, method: &mut syn::ImplItemFn) {
|
|
if method.sig.ident == "init" || method.sig.ident == "init_widgets" {
|
|
visit_mut::visit_impl_item_fn_mut(self, method);
|
|
|
|
if !self.expanded {
|
|
self.errors.push(syn::Error::new_spanned(method, "expected an injection point for the view macro. Try using `let widgets = view_output!();`"));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn visit_stmt_mut(&mut self, stmt: &mut syn::Stmt) {
|
|
let mut expand = false;
|
|
|
|
if let syn::Stmt::Local(syn::Local {
|
|
init: Some(LocalInit { expr, .. }),
|
|
..
|
|
}) = stmt
|
|
{
|
|
if let syn::Expr::Macro(mac) = &**expr {
|
|
if mac.mac.path.is_ident("view_output") {
|
|
expand = true;
|
|
}
|
|
}
|
|
|
|
if expand {
|
|
// Replace the macro invocation with the widget initialization code. Perform the
|
|
// swap in-place to avoid a clone.
|
|
mem::swap(expr, &mut self.widgets_init);
|
|
}
|
|
}
|
|
|
|
if expand {
|
|
let view_code = &self.view_code;
|
|
|
|
*stmt = syn::Stmt::Expr(
|
|
syn::Expr::Verbatim(quote! {
|
|
#view_code
|
|
#stmt
|
|
}),
|
|
None,
|
|
);
|
|
|
|
self.expanded = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns an empty impl item that can be used to remove an existing item in a mutable visitor.
|
|
fn null_item() -> syn::ImplItem {
|
|
syn::ImplItem::Verbatim(quote! {})
|
|
}
|