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
5 changes: 5 additions & 0 deletions .changes/add-macros-allow-rename-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"tauri-macros": minor:feat
---

Add support for rename command macro in tauri-macros
24 changes: 19 additions & 5 deletions crates/tauri-macros/src/command/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,14 +151,28 @@ impl From<Handler> for proc_macro::TokenStream {
) -> Self {
let cmd = format_ident!("__tauri_cmd__");
let invoke = format_ident!("__tauri_invoke__");
let (paths, attrs): (Vec<Path>, Vec<Vec<Attribute>>) = command_defs
.into_iter()
.map(|def| (def.path, def.attrs))
.unzip();
let mut paths: Vec<Path> = Vec::new();
let mut attrs: Vec<Vec<Attribute>> = Vec::new();
let mut command_name_consts: Vec<Path> = Vec::new();
for (def, command) in command_defs.into_iter().zip(commands) {
let path = def.path;
let attrs_vec = def.attrs;
let mut const_path = path.clone();
let last = const_path
.segments
.last_mut()
.expect("path has at least one segment");
let upper = command.to_string().to_uppercase();
last.ident = format_ident!("__TAURI_COMMAND_NAME_{}", upper);
paths.push(path);
attrs.push(attrs_vec);
command_name_consts.push(const_path);
}

quote::quote!(move |#invoke| {
let #cmd = #invoke.message.command();
match #cmd {
#(#(#attrs)* stringify!(#commands) => #wrappers!(#paths, #invoke),)*
#(#(#attrs)* #command_name_consts => #wrappers!(#paths, #invoke),)*
_ => {
return false;
},
Expand Down
69 changes: 63 additions & 6 deletions crates/tauri-macros/src/command/wrapper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct WrapperAttributes {
root: TokenStream2,
execution_context: ExecutionContext,
argument_case: ArgumentCase,
rename: RenamePolicy,
}

impl Parse for WrapperAttributes {
Expand All @@ -48,6 +49,7 @@ impl Parse for WrapperAttributes {
root: quote!(::tauri),
execution_context: ExecutionContext::Blocking,
argument_case: ArgumentCase::Camel,
rename: RenamePolicy::Keep,
};

let attrs = Punctuated::<WrapperAttributeKind, Token![,]>::parse_terminated(input)?;
Expand All @@ -74,6 +76,19 @@ impl Parse for WrapperAttributes {
}
};
}
} else if v.path.is_ident("rename") {
if let Expr::Lit(ExprLit {
lit: Lit::Str(s), ..
}) = v.value
{
let lit = s.value();
wrapper_attributes.rename = RenamePolicy::Rename(quote!(#lit));
} else {
return Err(syn::Error::new(
v.span(),
"expected string literal for rename",
));
}
} else if v.path.is_ident("root") {
if let Expr::Lit(ExprLit {
lit: Lit::Str(s),
Expand All @@ -94,7 +109,7 @@ impl Parse for WrapperAttributes {
WrapperAttributeKind::Meta(Meta::Path(_)) => {
return Err(syn::Error::new(
input.span(),
"unexpected input, expected one of `rename_all`, `root`, `async`",
"unexpected input, expected one of `rename_all`, `rename`, `root`, `async`",
));
}
WrapperAttributeKind::Async => {
Expand All @@ -120,6 +135,12 @@ enum ArgumentCase {
Camel,
}

/// The rename policy for the command.
enum RenamePolicy {
Keep,
Rename(TokenStream2),
}

/// The bindings we attach to `tauri::Invoke`.
struct Invoke {
message: Ident,
Expand All @@ -138,9 +159,13 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
attrs.execution_context = ExecutionContext::Async;
}

// macros used with `pub use my_macro;` need to be exported with `#[macro_export]`
let maybe_macro_export = match &function.vis {
Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]),
// macros used with `pub use my_macro;` need to be exported with `#[macro_export]`.
// To avoid crate-root name collisions for same-named commands across modules,
// only export non-renamed commands at crate root. Renamed commands remain module-scoped.
let maybe_macro_export = match (&attrs.rename, &function.vis) {
(RenamePolicy::Keep, Visibility::Public(_) | Visibility::Restricted(_)) => {
quote!(#[macro_export])
}
_ => TokenStream2::default(),
};

Expand Down Expand Up @@ -270,13 +295,40 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
TokenStream2::default()
};

// For renamed commands (no crate-root export), restrict rename visibility to crate-only
// to avoid public re-export errors for non-exported macros.
let rename_visibility = if let RenamePolicy::Rename(_) = &attrs.rename {
quote!(pub(crate))
} else {
quote!(#visibility)
};

// Always define a hidden constant holding the externally invoked command name.
// This lets the handler match on the renamed string while the original function
// identifier remains usable in `generate_handler![original_fn_name]`.
let command_name_const_ident = {
let upper = function.sig.ident.to_string().to_uppercase();
format_ident!("__TAURI_COMMAND_NAME_{}", upper)
};
let command_name_const_value = if let RenamePolicy::Rename(ref rename) = attrs.rename {
quote!(#rename)
} else {
let ident = &function.sig.ident;
quote!(stringify!(#ident))
};

// Rely on rust 2018 edition to allow importing a macro from a path.
quote!(
#async_command_check

#maybe_allow_unused
#function

// Command name constant used by the handler for pattern matching.
#[doc(hidden)]
#maybe_allow_unused
pub const #command_name_const_ident: &str = #command_name_const_value;

#maybe_allow_unused
#maybe_macro_export
#[doc(hidden)]
Expand All @@ -303,7 +355,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {

// allow the macro to be resolved with the same path as the command function
#[allow(unused_imports)]
#visibility use #wrapper;
#rename_visibility use #wrapper;
)
.into()
}
Expand Down Expand Up @@ -467,11 +519,16 @@ fn parse_arg(
}

let root = &attributes.root;
let command_name = if let RenamePolicy::Rename(r) = &attributes.rename {
quote!(stringify!(#r))
} else {
quote!(stringify!(#command))
};

Ok(quote!(#root::ipc::CommandArg::from_command(
#root::ipc::CommandItem {
plugin: #plugin_name,
name: stringify!(#command),
name: #command_name,
key: #key,
message: &#message,
acl: &#acl,
Expand Down
Loading