Skip to main content

typewriter_macros/
lib.rs

1//! # typewriter-macros
2//!
3//! Proc macro crate for the typewriter type sync SDK.
4//! Provides `#[derive(TypeWriter)]` with `#[sync_to(...)]` and `#[tw(...)]` attributes.
5
6use proc_macro::TokenStream;
7use std::path::PathBuf;
8
9/// Derive macro for typewriter type synchronization.
10///
11/// # Usage
12///
13/// ```rust,ignore
14/// use typebridge::TypeWriter;
15///
16/// #[derive(TypeWriter)]
17/// #[sync_to(typescript, python)]
18/// pub struct UserProfile {
19///     pub id: Uuid,
20///     pub email: String,
21///     pub age: Option<u32>,
22/// }
23/// ```
24///
25/// This will generate corresponding TypeScript and Python type definitions
26/// on `cargo build`.
27#[proc_macro_derive(TypeWriter, attributes(sync_to, tw))]
28pub fn derive_typewriter(input: TokenStream) -> TokenStream {
29    let input = syn::parse_macro_input!(input as syn::DeriveInput);
30
31    match typewriter_impl(&input) {
32        Ok(_) => TokenStream::new(),
33        Err(err) => err.to_compile_error().into(),
34    }
35}
36
37fn typewriter_impl(input: &syn::DeriveInput) -> syn::Result<()> {
38    let type_def = typewriter_engine::parser::parse_type_def(input)?;
39    let targets = typewriter_engine::parser::parse_sync_to_attr(input)?;
40    let zod_schema = typewriter_engine::parser::parse_tw_zod_attr(input)?;
41
42    if targets.is_empty() {
43        return Err(syn::Error::new_spanned(
44            &input.ident,
45            "typewriter: #[sync_to(...)] attribute is required. \
46             Example: #[sync_to(typescript, python)]",
47        ));
48    }
49
50    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
51    let manifest_dir = PathBuf::from(manifest_dir);
52    let project_root = typewriter_engine::project::discover_macro_root(&manifest_dir);
53    let config = typewriter_engine::project::load_config_or_default(&project_root);
54
55    let spec = typewriter_engine::TypeSpec {
56        type_def,
57        targets,
58        source_path: manifest_dir.join("<proc-macro>"),
59        zod_schema,
60    };
61
62    let files =
63        match typewriter_engine::emit::render_specs(&[spec], &project_root, &config, &[], true) {
64            Ok(files) => files,
65            Err(err) => {
66                eprintln!("typewriter: generation failed for {}: {}", input.ident, err);
67                return Ok(());
68            }
69        };
70
71    if let Err(err) = typewriter_engine::emit::write_generated_files(&files) {
72        eprintln!("typewriter: failed to write generated files: {}", err);
73        return Ok(());
74    }
75
76    for file in files {
77        eprintln!(
78            "  typewriter: {} → {}",
79            file.type_name,
80            file.output_path.display()
81        );
82    }
83
84    Ok(())
85}