aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/crates/macros/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/macros/src')
-rw-r--r--crates/macros/src/lib.rs182
1 files changed, 43 insertions, 139 deletions
diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs
index 16db1a9..21c8bf7 100644
--- a/crates/macros/src/lib.rs
+++ b/crates/macros/src/lib.rs
@@ -2,29 +2,13 @@ use heck::ToLowerCamelCase;
use proc_macro::TokenStream;
use quote::quote;
use std::collections::BTreeSet;
-use syn::{
- parse::Parse, parse::ParseStream, parse_macro_input, punctuated::Punctuated, token::Comma,
- Expr, FnArg, Ident, ItemFn, Lit, MetaNameValue, Pat, PathArguments, ReturnType, Type,
-};
+use syn::{parse_macro_input, FnArg, Ident, ItemFn, Pat, PathArguments, ReturnType, Type};
use crate::attr::MacroArgs;
mod attr;
-fn get_lit_str_value(nv: &MetaNameValue) -> Option<String> {
- // In syn v2 MetaNameValue.value is an Expr (usually Expr::Lit). Extract string literal if present.
- match &nv.value {
- Expr::Lit(expr_lit) => {
- if let Lit::Str(s) = &expr_lit.lit {
- Some(s.value())
- } else {
- None
- }
- }
- _ => None,
- }
-}
-
+const TAURI_NATIVE_TYPES: &[&str] = &["Window", "State", "AppHandle"];
fn is_tauri_native(ty: &Type) -> bool {
// Unwrap reference
let mut t = ty;
@@ -35,7 +19,7 @@ fn is_tauri_native(ty: &Type) -> bool {
if let Type::Path(p) = t {
if let Some(seg) = p.path.segments.last() {
let ident = seg.ident.to_string();
- if ident == "Window" || ident == "State" {
+ if TAURI_NATIVE_TYPES.contains(&ident.as_ref()) {
return true;
}
}
@@ -54,14 +38,15 @@ fn extract_ident_from_type(ty: &Type) -> Option<String> {
// Handle Option<T>, Result, etc.
if let Some(seg) = p.path.segments.last() {
let ident = seg.ident.to_string();
- if ident == "Option" {
- // extract generic arg (use helper)
- if let Some(inner) = first_type_arg_from_pathargs(&seg.arguments) {
- return extract_ident_from_type(inner);
+ match ident.as_str() {
+ "Option" | "Vec" => {
+ // extract generic arg (use helper)
+ if let Some(inner) = first_type_arg_from_pathargs(&seg.arguments) {
+ return extract_ident_from_type(inner);
+ }
}
- } else {
// For multi-segment like core::java::JavaDownloadInfo we return last segment ident
- return Some(ident);
+ _ => return Some(ident),
}
}
}
@@ -90,6 +75,12 @@ fn rust_type_to_ts(ty: &Type) -> (String, bool) {
t = &*r.elem;
}
+ if let Type::Tuple(tuple) = t {
+ if tuple.elems.is_empty() {
+ return ("void".to_string(), false);
+ }
+ }
+
if let Type::Path(p) = t {
if let Some(seg) = p.path.segments.last() {
let ident = seg.ident.to_string();
@@ -172,13 +163,6 @@ pub fn api(attr: TokenStream, item: TokenStream) -> TokenStream {
Err(err) => return err.into_compile_error().into(),
};
let input_fn = parse_macro_input!(item as ItemFn);
-
- // Extract attribute args: export_to, import_from
- let export_to = match (meta.export_to, meta.export_to_path) {
- (Some(to), None) => Some(to),
- (_, Some(path)) => Some(path),
- (None, None) => None,
- };
let import_from = meta.import_from;
// Analyze function
@@ -232,120 +216,44 @@ pub fn api(attr: TokenStream, item: TokenStream) -> TokenStream {
// Return type
let (return_ts_promise, return_imports) = get_return_ts(&input_fn.sig.output);
- for r in return_imports {
- import_types.insert(r);
- }
-
- // Build TypeScript code string
- // let mut ts_lines: Vec<String> = Vec::new();
-
- // ts_lines.push(r#"import { invoke } from "@tauri-apps/api/core""#.to_string());
-
- // if !import_types.is_empty() {
- // if let Some(import_from_str) = import_from.clone() {
- // let types_joined = import_types.iter().cloned().collect::<Vec<_>>().join(", ");
- // ts_lines.push(format!(
- // "import {{ {} }} from \"{}\"",
- // types_joined, import_from_str
- // ));
- // } else {
- // // If no import_from provided, still import types from local path? We'll skip if not provided.
- // }
- // }
-
- // function signature
- let params_sig = param_defs.join(", ");
- let params_pass = if param_names.is_empty() {
- "".to_string()
- } else {
- // Build object like { majorVersion, imageType }
- format!("{}", param_names.join(", "))
- };
-
- // Determine return generic for invoke: need the raw type (not Promise<...>)
- let invoke_generic =
- if return_ts_promise.starts_with("Promise<") && return_ts_promise.ends_with('>') {
- &return_ts_promise["Promise<".len()..return_ts_promise.len() - 1]
- } else {
- "unknown"
- };
-
- let invoke_line = if param_names.is_empty() {
- format!(" return invoke<{}>(\"{}\")", invoke_generic, fn_name)
- } else {
- format!(
- " return invoke<{}>(\"{}\", {{ {} }})",
- invoke_generic, fn_name, params_pass
- )
- };
-
- // ts_lines.push(format!(
- // "export async function {}({}): {} {{",
- // ts_fn_name, params_sig, return_ts_promise
- // ));
- // ts_lines.push(invoke_line);
- // ts_lines.push("}".to_string());
-
- // let ts_contents = ts_lines.join("\n") + "\n";
+ eprintln!(
+ "return {} from {} of {}",
+ return_ts_promise,
+ return_imports.iter().cloned().collect::<Vec<_>>().join(","),
+ fn_name
+ );
+ import_types.extend(return_imports);
+ // Prepare test mod name
+ let test_mod_name = Ident::new(
+ &format!("__dropout_export_tests_{}", fn_name),
+ fn_name_ident.span(),
+ );
// Prepare test function name
let test_fn_name = Ident::new(
&format!("tauri_export_bindings_{}", fn_name),
fn_name_ident.span(),
);
- // Generate code for test function that writes the TS string to file
- let export_to_literal = match export_to {
- Some(ref s) => s.clone(),
- None => String::new(),
- };
-
// Build tokens
let original_fn = &input_fn;
- // let ts_string_literal = ts_contents.clone();
+ let return_ts_promise_lit =
+ syn::LitStr::new(&return_ts_promise, proc_macro2::Span::call_site());
+ let import_from_lit = match &import_from {
+ Some(s) => syn::Ident::new(&format!("Some({})", s), proc_macro2::Span::call_site()),
+ None => syn::Ident::new("None", proc_macro2::Span::call_site()),
+ };
- // let write_stmt = if export_to_literal.is_empty() {
- // // No-op: don't write
- // // quote! {
- // // // No export_to provided; skipping file write.
- // // }
- // panic!("No export_to provided")
- // } else {
- // // We'll append to the file to avoid overwriting existing bindings from other macros.
- // // Use create(true).append(true)
- // let path = export_to_literal.clone();
- // let ts_lit = syn::LitStr::new(&ts_string_literal, proc_macro2::Span::call_site());
- // quote! {
- // {
- // // Ensure parent directories exist if possible (best-effort)
- // let path = std::path::Path::new(#path);
- // if let Some(parent) = path.parent() {
- // let _ = std::fs::create_dir_all(parent);
- // }
- // // Append generated bindings to file
- // match OpenOptions::new().create(true).append(true).open(path) {
- // Ok(mut f) => {
- // let _ = f.write_all(#ts_lit.as_bytes());
- // println!("Successfully wrote to {}", path.display());
- // }
- // Err(e) => {
- // eprintln!("dropout::api binding write failed: {}", e);
- // }
- // }
- // }
- // }
- // };
let register_stmt = quote! {
- ::dropout_core::inventory::submit! {
- ::dropout_core::ApiInfo {
+ ::inventory::submit! {
+ crate::utils::api::ApiInfo {
fn_name: #fn_name,
ts_fn_name: #ts_fn_name,
- param_names: vec![#(#param_names),*],
- param_defs: vec![#(#param_defs),*],
- return_ts_promise: #return_ts_promise,
- import_types: BTreeSet::from([#(#import_types),*]),
- import_from: #import_from,
- export_to: #export_to_literal,
+ param_names: &[#(#param_names),*],
+ param_defs: &[#(#param_defs),*],
+ return_ts_promise: #return_ts_promise_lit,
+ import_types: &[#(#import_types),*],
+ import_from: #import_from_lit,
}
}
};
@@ -354,15 +262,11 @@ pub fn api(attr: TokenStream, item: TokenStream) -> TokenStream {
#original_fn
#[cfg(test)]
- mod __dropout_export_tests {
- use super::*;
- use std::fs::OpenOptions;
- use std::io::Write;
+ mod #test_mod_name {
+ use crate::utils::api::*;
#[test]
fn #test_fn_name() {
- // Generated TypeScript bindings for function: #fn_name
- // #write_stmt
#register_stmt
}
}