Skip to content

Commit c00a3db

Browse files
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>
1 parent 764b913 commit c00a3db

6 files changed

Lines changed: 101 additions & 13 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri-macros": minor:feat
3+
"tauri": minor:feat
4+
---
5+
6+
Add support for the `rename` attribute in the `tauri::command` macro to allow renaming the command to something other than the function name.

crates/tauri-macros/src/command/handler.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// SPDX-License-Identifier: Apache-2.0
33
// SPDX-License-Identifier: MIT
44

5-
use quote::format_ident;
5+
use quote::{format_ident, quote};
66
use syn::{
77
parse::{Parse, ParseBuffer, ParseStream},
88
Attribute, Ident, Path, Token,
@@ -151,14 +151,30 @@ impl From<Handler> for proc_macro::TokenStream {
151151
) -> Self {
152152
let cmd = format_ident!("__tauri_cmd__");
153153
let invoke = format_ident!("__tauri_invoke__");
154-
let (paths, attrs): (Vec<Path>, Vec<Vec<Attribute>>) = command_defs
155-
.into_iter()
156-
.map(|def| (def.path, def.attrs))
157-
.unzip();
154+
let mut paths: Vec<Path> = Vec::new();
155+
let mut attrs: Vec<Vec<Attribute>> = Vec::new();
156+
let mut command_name_macros: Vec<proc_macro2::TokenStream> = Vec::new();
157+
for (def, command) in command_defs.into_iter().zip(commands) {
158+
let path = def.path;
159+
let attrs_vec = def.attrs;
160+
161+
let mut command_name_macro_path = path.clone();
162+
let last = command_name_macro_path
163+
.segments
164+
.last_mut()
165+
.expect("path has at least one segment");
166+
last.ident = format_ident!("__tauri_command_name_{command}");
167+
168+
paths.push(path);
169+
attrs.push(attrs_vec);
170+
// Call the macro to get the command name string literal
171+
command_name_macros.push(quote!(#command_name_macro_path!()));
172+
}
173+
158174
quote::quote!(move |#invoke| {
159175
let #cmd = #invoke.message.command();
160176
match #cmd {
161-
#(#(#attrs)* stringify!(#commands) => #wrappers!(#paths, #invoke),)*
177+
#(#(#attrs)* #command_name_macros => #wrappers!(#paths, #invoke),)*
162178
_ => {
163179
return false;
164180
},

crates/tauri-macros/src/command/wrapper.rs

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ struct WrapperAttributes {
4040
root: TokenStream2,
4141
execution_context: ExecutionContext,
4242
argument_case: ArgumentCase,
43+
rename: RenamePolicy,
4344
}
4445

4546
impl Parse for WrapperAttributes {
@@ -48,6 +49,7 @@ impl Parse for WrapperAttributes {
4849
root: quote!(::tauri),
4950
execution_context: ExecutionContext::Blocking,
5051
argument_case: ArgumentCase::Camel,
52+
rename: RenamePolicy::Keep,
5153
};
5254

5355
let attrs = Punctuated::<WrapperAttributeKind, Token![,]>::parse_terminated(input)?;
@@ -74,6 +76,19 @@ impl Parse for WrapperAttributes {
7476
}
7577
};
7678
}
79+
} else if v.path.is_ident("rename") {
80+
if let Expr::Lit(ExprLit {
81+
lit: Lit::Str(s), ..
82+
}) = v.value
83+
{
84+
let lit = s.value();
85+
wrapper_attributes.rename = RenamePolicy::Rename(quote!(#lit));
86+
} else {
87+
return Err(syn::Error::new(
88+
v.span(),
89+
"expected string literal for rename",
90+
));
91+
}
7792
} else if v.path.is_ident("root") {
7893
if let Expr::Lit(ExprLit {
7994
lit: Lit::Str(s),
@@ -94,7 +109,7 @@ impl Parse for WrapperAttributes {
94109
WrapperAttributeKind::Meta(Meta::Path(_)) => {
95110
return Err(syn::Error::new(
96111
input.span(),
97-
"unexpected input, expected one of `rename_all`, `root`, `async`",
112+
"unexpected input, expected one of `rename_all`, `rename`, `root`, `async`",
98113
));
99114
}
100115
WrapperAttributeKind::Async => {
@@ -120,6 +135,12 @@ enum ArgumentCase {
120135
Camel,
121136
}
122137

138+
/// The rename policy for the command.
139+
enum RenamePolicy {
140+
Keep,
141+
Rename(TokenStream2),
142+
}
143+
123144
/// The bindings we attach to `tauri::Invoke`.
124145
struct Invoke {
125146
message: Ident,
@@ -138,9 +159,11 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
138159
attrs.execution_context = ExecutionContext::Async;
139160
}
140161

141-
// macros used with `pub use my_macro;` need to be exported with `#[macro_export]`
162+
// macros used with `pub use my_macro;` need to be exported with `#[macro_export]`.
142163
let maybe_macro_export = match &function.vis {
143-
Visibility::Public(_) | Visibility::Restricted(_) => quote!(#[macro_export]),
164+
Visibility::Public(_) | Visibility::Restricted(_) => {
165+
quote!(#[macro_export])
166+
}
144167
_ => TokenStream2::default(),
145168
};
146169

@@ -270,13 +293,35 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
270293
TokenStream2::default()
271294
};
272295

296+
// Always define a hidden macro that returns the externally invoked command name.
297+
// This lets the handler match on the renamed string while the original function
298+
// identifier remains usable in `generate_handler![original_fn_name]`.
299+
let command_name_macro_ident = format_ident!("__tauri_command_name_{}", function.sig.ident);
300+
let command_name_value = if let RenamePolicy::Rename(ref rename) = attrs.rename {
301+
quote!(#rename)
302+
} else {
303+
let ident = &function.sig.ident;
304+
quote!(stringify!(#ident))
305+
};
306+
273307
// Rely on rust 2018 edition to allow importing a macro from a path.
274308
quote!(
275309
#async_command_check
276310

277311
#maybe_allow_unused
278312
#function
279313

314+
// Command name macro used by the handler for pattern matching.
315+
// This macro returns the command name string literal (renamed or original).
316+
#maybe_allow_unused
317+
#maybe_macro_export
318+
#[doc(hidden)]
319+
macro_rules! #command_name_macro_ident {
320+
() => {
321+
#command_name_value
322+
};
323+
}
324+
280325
#maybe_allow_unused
281326
#maybe_macro_export
282327
#[doc(hidden)]
@@ -303,7 +348,7 @@ pub fn wrapper(attributes: TokenStream, item: TokenStream) -> TokenStream {
303348

304349
// allow the macro to be resolved with the same path as the command function
305350
#[allow(unused_imports)]
306-
#visibility use #wrapper;
351+
#visibility use {#wrapper, #command_name_macro_ident};
307352
)
308353
.into()
309354
}
@@ -467,11 +512,16 @@ fn parse_arg(
467512
}
468513

469514
let root = &attributes.root;
515+
let command_name = if let RenamePolicy::Rename(r) = &attributes.rename {
516+
quote!(stringify!(#r))
517+
} else {
518+
quote!(stringify!(#command))
519+
};
470520

471521
Ok(quote!(#root::ipc::CommandArg::from_command(
472522
#root::ipc::CommandItem {
473523
plugin: #plugin_name,
474-
name: stringify!(#command),
524+
name: #command_name,
475525
key: #key,
476526
message: &#message,
477527
acl: &#acl,

examples/commands/commands.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,8 @@ pub fn simple_command(the_argument: String) {
2525
pub fn stateful_command(the_argument: Option<String>, state: State<'_, super::MyState>) {
2626
println!("{:?} {:?}", the_argument, state.inner());
2727
}
28+
29+
#[command(rename = "renamed_command_in_mod_new")]
30+
pub fn renamed_command_in_mod() {
31+
println!("renamed command in mod called");
32+
}

examples/commands/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ <h1>Tauri Commands</h1>
6363
{
6464
name: 'command_arguments_tuple_struct',
6565
args: { inlinePerson: ['ferris', 6] }
66-
}
66+
},
67+
{ name: 'renamed_command' },
68+
{ name: 'renamed_command_new' },
69+
{ name: 'renamed_command_in_mod' },
70+
{ name: 'renamed_command_in_mod_new' }
6771
]
6872

6973
for (const command of commands) {

examples/commands/main.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
// we move some basic commands to a separate module just to show it works
88
mod commands;
9-
use commands::{cmd, invoke, message, resolver};
9+
use commands::{cmd, invoke, message, renamed_command_in_mod, resolver};
1010

1111
use serde::Deserialize;
1212
use tauri::{
@@ -188,6 +188,11 @@ fn command_arguments_wild(_: Window) {
188188
println!("we saw the wildcard!")
189189
}
190190

191+
#[command(rename = "renamed_command_new")]
192+
fn renamed_command() {
193+
println!("renamed command called")
194+
}
195+
191196
#[derive(Deserialize)]
192197
struct Person<'a> {
193198
name: &'a str,
@@ -246,6 +251,8 @@ fn main() {
246251
future_simple_command,
247252
async_stateful_command,
248253
command_arguments_wild,
254+
renamed_command,
255+
renamed_command_in_mod,
249256
command_arguments_struct,
250257
simple_command_with_result,
251258
async_simple_command_snake,

0 commit comments

Comments
 (0)