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
6mod emitter;
7mod parser;
8
9use proc_macro::TokenStream;
10
11/// Derive macro for typewriter type synchronization.
12///
13/// # Usage
14///
15/// ```rust,ignore
16/// use typebridge::TypeWriter;
17///
18/// #[derive(TypeWriter)]
19/// #[sync_to(typescript, python)]
20/// pub struct UserProfile {
21///     pub id: Uuid,
22///     pub email: String,
23///     pub age: Option<u32>,
24/// }
25/// ```
26///
27/// This will generate corresponding TypeScript and Python type definitions
28/// on `cargo build`.
29#[proc_macro_derive(TypeWriter, attributes(sync_to, tw))]
30pub fn derive_typewriter(input: TokenStream) -> TokenStream {
31    let input = syn::parse_macro_input!(input as syn::DeriveInput);
32
33    match typewriter_impl(&input) {
34        Ok(_) => TokenStream::new(),
35        Err(err) => err.to_compile_error().into(),
36    }
37}
38
39fn typewriter_impl(input: &syn::DeriveInput) -> syn::Result<()> {
40    // 1. Parse the derive input into our IR
41    let type_def = parser::parse_type_def(input)?;
42
43    // 2. Extract target languages from #[sync_to(...)]
44    let targets = parser::parse_sync_to_attr(input)?;
45
46    if targets.is_empty() {
47        return Err(syn::Error::new_spanned(
48            &input.ident,
49            "typewriter: #[sync_to(...)] attribute is required. \
50             Example: #[sync_to(typescript, python)]",
51        ));
52    }
53
54    // 3. Load config (typewriter.toml) - look upwards from CARGO_MANIFEST_DIR
55    let config = load_config();
56
57    // 4. Emit to each target language
58    emitter::emit_all(&type_def, &targets, &config);
59
60    Ok(())
61}
62
63/// Try to load typewriter.toml from the project root.
64fn load_config() -> typewriter_core::config::TypewriterConfig {
65    // In a proc macro context, CARGO_MANIFEST_DIR points to the crate being compiled
66    if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
67        let path = std::path::Path::new(&manifest_dir);
68        // Try the manifest dir itself, then parent dirs
69        for ancestor in path.ancestors() {
70            if let Ok(config) = typewriter_core::config::TypewriterConfig::load(ancestor) {
71                if config.typescript.is_some() || config.python.is_some() {
72                    return config;
73                }
74            }
75        }
76    }
77    typewriter_core::config::TypewriterConfig::default()
78}