feat(macros): add support for rename command macro in tauri-macros #14173 (#14473)

* feat(macros): add support for alias command macro in tauri-macros #14173

* feat(macro): rename alias command to improve clarity in tauri-macros

* feat(wrapper): refactor rename handling in WrapperAttributes for improved clarity

* feat(wrapper): update rename policy to use TokenStream2 for improved flexibility

* feat(handler): streamline command definition parsing for improved efficiency

* feat(wrapper): simplify macro export logic in wrapper function for clarity

* fix(handler): optimize command zipping for improved readability

* fix: code style compectiable with rust 1.77.2

* fix const not in scope when command is defined in another mod

* update examples

* update change file

* fmt

* style: fix pnpm:check errors

---------

Co-authored-by: Lucas Nogueira <lucas@tauri.app>
This commit is contained in:
TungliesandLucas Nogueira authored and GitHub committed 2026-04-30 10:11:59 -03:00
1 parent 764b9139a3
commit c00a3dbffc
6 files changed
+101 -13

No files matched your search

@@ -0,0 +1,6 @@
---
"tauri-macros": minor:feat
"tauri": minor:feat
---
Add support for the `rename` attribute in the `tauri::command` macro to allow renaming the command to something other than the function name.
+22 -6
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use quote::format_ident;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseBuffer, ParseStream},
Attribute, Ident, Path, Token,
@@ -151,14 +151,30 @@ 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_macros: Vec<proc_macro2::TokenStream> = Vec::new();
for (def, command) in command_defs.into_iter().zip(commands) {
let path = def.path;
let attrs_vec = def.attrs;
let mut command_name_macro_path = path.clone();
let last = command_name_macro_path
.segments
.last_mut()
.expect("path has at least one segment");
last.ident = format_ident!("__tauri_command_name_{command}");
paths.push(path);
attrs.push(attrs_vec);
// Call the macro to get the command name string literal
command_name_macros.push(quote!(#command_name_macro_path!()));
}
quote::quote!(move |#invoke| {
let #cmd = #invoke.message.command();
match #cmd {
#(#(#attrs)* stringify!(#commands) => #wrappers!(#paths, #invoke),)*
#(#(#attrs)* #command_name_macros => #wrappers!(#paths, #invoke),)*
_ => {
return false;
},
+55 -5
View File
@@ -40,6 +40,7 @@ struct WrapperAttributes {
root: TokenStream2,
execution_context: ExecutionContext,
argument_case: ArgumentCase,
rename: RenamePolicy,
}
impl Parse for WrapperAttributes {
@@ -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)?;
@@ -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),
@@ -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 => {
@@ -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,
@@ -138,9 +159,11 @@ 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]`
// 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]),
Visibility::Public(_) | Visibility::Restricted(_) => {
quote!(#[macro_export])
}
_ => TokenStream2::default(),
};
@@ -270,6 +293,17 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
TokenStream2::default()
};
// Always define a hidden macro that returns 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_macro_ident = format_ident!("__tauri_command_name_{}", function.sig.ident);
let command_name_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
@@ -277,6 +311,17 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
#maybe_allow_unused
#function
// Command name macro used by the handler for pattern matching.
// This macro returns the command name string literal (renamed or original).
#maybe_allow_unused
#maybe_macro_export
#[doc(hidden)]
macro_rules! #command_name_macro_ident {
() => {
#command_name_value
};
}
#maybe_allow_unused
#maybe_macro_export
#[doc(hidden)]
@@ -303,7 +348,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;
#visibility use {#wrapper, #command_name_macro_ident};
)
.into()
}
@@ -467,11 +512,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,
+5
View File
@@ -25,3 +25,8 @@ pub fn simple_command(the_argument: String) {
pub fn stateful_command(the_argument: Option<String>, state: State<'_, super::MyState>) {
println!("{:?} {:?}", the_argument, state.inner());
}
#[command(rename = "renamed_command_in_mod_new")]
pub fn renamed_command_in_mod() {
println!("renamed command in mod called");
}
+5 -1
View File
@@ -63,7 +63,11 @@
{
name: 'command_arguments_tuple_struct',
args: { inlinePerson: ['ferris', 6] }
}
},
{ name: 'renamed_command' },
{ name: 'renamed_command_new' },
{ name: 'renamed_command_in_mod' },
{ name: 'renamed_command_in_mod_new' }
]
for (const command of commands) {
+8 -1
View File
@@ -6,7 +6,7 @@
// we move some basic commands to a separate module just to show it works
mod commands;
use commands::{cmd, invoke, message, resolver};
use commands::{cmd, invoke, message, renamed_command_in_mod, resolver};
use serde::Deserialize;
use tauri::{
@@ -188,6 +188,11 @@ fn command_arguments_wild(_: Window) {
println!("we saw the wildcard!")
}
#[command(rename = "renamed_command_new")]
fn renamed_command() {
println!("renamed command called")
}
#[derive(Deserialize)]
struct Person<'a> {
name: &'a str,
@@ -246,6 +251,8 @@ fn main() {
future_simple_command,
async_stateful_command,
command_arguments_wild,
renamed_command,
renamed_command_in_mod,
command_arguments_struct,
simple_command_with_result,
async_simple_command_snake,