macroforge_ts_syn/abi/
patch.rs

1use serde::{Deserialize, Serialize};
2
3use crate::abi::{swc_ast, SpanIR};
4#[cfg(feature = "swc")]
5use swc_core::common::{SyntaxContext, DUMMY_SP};
6
7/// Patch-based output = stable "quote" target.
8#[derive(Serialize, Deserialize)]
9#[derive(Clone, Debug, PartialEq)]
10pub enum Patch {
11    Insert {
12        at: SpanIR,
13        code: PatchCode,
14        /// Which macro generated this patch (e.g., "Debug", "Clone")
15        #[serde(default)]
16        source_macro: Option<String>,
17    },
18    Replace {
19        span: SpanIR,
20        code: PatchCode,
21        /// Which macro generated this patch (e.g., "Debug", "Clone")
22        #[serde(default)]
23        source_macro: Option<String>,
24    },
25    Delete {
26        span: SpanIR,
27    },
28    InsertRaw {
29        at: SpanIR,
30        code: String,
31        context: Option<String>,
32        /// Which macro generated this patch (e.g., "Debug", "Clone")
33        #[serde(default)]
34        source_macro: Option<String>,
35    },
36    ReplaceRaw {
37        span: SpanIR,
38        code: String,
39        context: Option<String>,
40        /// Which macro generated this patch (e.g., "Debug", "Clone")
41        #[serde(default)]
42        source_macro: Option<String>,
43    },
44}
45
46impl Patch {
47    /// Get the source macro name for this patch, if set
48    pub fn source_macro(&self) -> Option<&str> {
49        match self {
50            Patch::Insert { source_macro, .. } => source_macro.as_deref(),
51            Patch::Replace { source_macro, .. } => source_macro.as_deref(),
52            Patch::Delete { .. } => None,
53            Patch::InsertRaw { source_macro, .. } => source_macro.as_deref(),
54            Patch::ReplaceRaw { source_macro, .. } => source_macro.as_deref(),
55        }
56    }
57
58    /// Set the source macro name for this patch
59    pub fn with_source_macro(self, macro_name: &str) -> Self {
60        match self {
61            Patch::Insert { at, code, .. } => Patch::Insert {
62                at,
63                code,
64                source_macro: Some(macro_name.to_string()),
65            },
66            Patch::Replace { span, code, .. } => Patch::Replace {
67                span,
68                code,
69                source_macro: Some(macro_name.to_string()),
70            },
71            Patch::Delete { span } => Patch::Delete { span },
72            Patch::InsertRaw { at, code, context, .. } => Patch::InsertRaw {
73                at,
74                code,
75                context,
76                source_macro: Some(macro_name.to_string()),
77            },
78            Patch::ReplaceRaw { span, code, context, .. } => Patch::ReplaceRaw {
79                span,
80                code,
81                context,
82                source_macro: Some(macro_name.to_string()),
83            },
84        }
85    }
86}
87
88#[derive(Clone, Debug, PartialEq)]
89pub enum PatchCode {
90    Text(String),
91    ClassMember(swc_ast::ClassMember),
92    Stmt(swc_ast::Stmt),
93    ModuleItem(swc_ast::ModuleItem),
94}
95
96// Custom serde for PatchCode - only serialize Text variant, skip AST variants
97impl serde::Serialize for PatchCode {
98    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
99    where
100        S: serde::Serializer,
101    {
102        match self {
103            PatchCode::Text(s) => serializer.serialize_str(s),
104            _ => serializer.serialize_str("/* AST node - cannot serialize */"),
105        }
106    }
107}
108
109impl<'de> serde::Deserialize<'de> for PatchCode {
110    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
111    where
112        D: serde::Deserializer<'de>,
113    {
114        let s = String::deserialize(deserializer)?;
115        Ok(PatchCode::Text(s))
116    }
117}
118
119impl From<String> for PatchCode {
120    fn from(value: String) -> Self {
121        PatchCode::Text(value)
122    }
123}
124
125impl From<&str> for PatchCode {
126    fn from(value: &str) -> Self {
127        PatchCode::Text(value.to_string())
128    }
129}
130
131impl From<swc_ast::ClassMember> for PatchCode {
132    fn from(member: swc_ast::ClassMember) -> Self {
133        PatchCode::ClassMember(member)
134    }
135}
136
137impl From<swc_ast::Stmt> for PatchCode {
138    fn from(stmt: swc_ast::Stmt) -> Self {
139        PatchCode::Stmt(stmt)
140    }
141}
142
143impl From<swc_ast::ModuleItem> for PatchCode {
144    fn from(item: swc_ast::ModuleItem) -> Self {
145        PatchCode::ModuleItem(item)
146    }
147}
148
149impl From<Vec<swc_ast::Stmt>> for PatchCode {
150    fn from(stmts: Vec<swc_ast::Stmt>) -> Self {
151        // For Vec<Stmt>, wrap in a block and convert to a single Stmt
152        if stmts.len() == 1 {
153            PatchCode::Stmt(stmts.into_iter().next().unwrap())
154        } else {
155            PatchCode::Stmt(swc_ast::Stmt::Block(swc_ast::BlockStmt {
156                span: DUMMY_SP,
157                ctxt: SyntaxContext::empty(),
158                stmts,
159            }))
160        }
161    }
162}
163
164impl From<Vec<swc_ast::ModuleItem>> for PatchCode {
165    fn from(items: Vec<swc_ast::ModuleItem>) -> Self {
166        // For Vec<ModuleItem>, take the first if there's only one
167        if items.len() == 1 {
168            PatchCode::ModuleItem(items.into_iter().next().unwrap())
169        } else {
170            // Multiple items - convert to a string representation
171            // This is a limitation since PatchCode doesn't have a Vec variant
172            let code = items
173                .iter()
174                .map(|_| "/* generated code */")
175                .collect::<Vec<_>>()
176                .join("\n");
177            PatchCode::Text(code)
178        }
179    }
180}
181
182#[derive(Serialize, Deserialize)]
183#[derive(Clone, Debug, Default)]
184pub struct MacroResult {
185    /// Patches to apply to the runtime JS/TS code
186    pub runtime_patches: Vec<Patch>,
187    /// Patches to apply to the .d.ts type declarations
188    pub type_patches: Vec<Patch>,
189    /// Diagnostic messages (errors, warnings, info)
190    pub diagnostics: Vec<Diagnostic>,
191    /// Optional raw token stream (source code) returned by the macro
192    pub tokens: Option<String>,
193    /// Optional debug information for development
194    pub debug: Option<String>,
195}
196
197#[derive(Serialize, Deserialize)]
198#[derive(Clone, Debug, PartialEq)]
199pub struct Diagnostic {
200    pub level: DiagnosticLevel,
201    pub message: String,
202    pub span: Option<SpanIR>,
203    /// Additional notes about the diagnostic
204    pub notes: Vec<String>,
205    /// Optional help text suggesting fixes
206    pub help: Option<String>,
207}
208
209#[derive(Serialize, Deserialize)]
210#[derive(Clone, Debug, PartialEq, Eq)]
211pub enum DiagnosticLevel {
212    Error,
213    Warning,
214    Info,
215}