Skip to main content

macroforge_ts_syn/
derive.rs

1//! `syn`-like derive input types for TypeScript macros.
2//!
3//! This module provides a [`DeriveInput`] type analogous to `syn::DeriveInput`,
4//! making it easy to write derive macros with a familiar API. If you've written
5//! proc macros in Rust using `syn`, this API will feel very natural.
6//!
7//! ## Overview
8//!
9//! The derive input system provides:
10//!
11//! - [`DeriveInput`] - The main entry point, containing the type name, attributes, and data
12//! - [`Data`] - An enum distinguishing classes, enums, interfaces, and type aliases
13//! - [`DataClass`], [`DataEnum`], [`DataInterface`], [`DataTypeAlias`] - Type-specific data
14//! - [`Ident`] - A simple identifier type with span information
15//! - [`Attribute`] - Decorator/attribute representation
16//!
17//! ## Comparison with syn
18//!
19//! | syn | macroforge_ts_syn | Notes |
20//! |-----|-------------------|-------|
21//! | `syn::DeriveInput` | [`DeriveInput`] | Same purpose |
22//! | `syn::Ident` | [`Ident`] | Same purpose |
23//! | `syn::Data` | [`Data`] | Includes more variants (Interface, TypeAlias) |
24//! | `syn::DataStruct` | [`DataClass`] | TypeScript classes ≈ Rust structs |
25//! | `syn::DataEnum` | [`DataEnum`] | Same purpose |
26//! | `syn::Attribute` | [`Attribute`] | Wraps JSDoc/TS decorators |
27//!
28//! ## Example: Basic Derive Macro
29//!
30//! ```rust,ignore
31//! use macroforge_ts_syn::{parse_ts_macro_input, DeriveInput, Data, MacroResult, TsStream};
32//!
33//! // This function signature shows how derive macros receive input
34//! pub fn my_macro(mut input: TsStream) -> MacroResult {
35//!     let input = parse_ts_macro_input!(input as DeriveInput);
36//!
37//!     // Get the type name
38//!     let _name = input.name();
39//!
40//!     match &input.data {
41//!         Data::Class(class) => {
42//!             // Access class fields and methods
43//!             for field in class.fields() {
44//!                 println!("Field: {} ({})", field.name, field.ts_type);
45//!             }
46//!         }
47//!         Data::Enum(enum_) => {
48//!             // Access enum variants
49//!             for variant in enum_.variants() {
50//!                 println!("Variant: {}", variant.name);
51//!             }
52//!         }
53//!         Data::Interface(iface) => {
54//!             // Access interface fields and methods
55//!             for field in iface.fields() {
56//!                 println!("Property: {}", field.name);
57//!             }
58//!         }
59//!         Data::TypeAlias(alias) => {
60//!             // Handle union types, object types, etc.
61//!             if let Some(members) = alias.as_union() {
62//!                 println!("Union with {} variants", members.len());
63//!             }
64//!         }
65//!     }
66//!
67//!     MacroResult::default()
68//! }
69//! ```
70//!
71//! ## Example: Accessing Decorators
72//!
73//! ```rust,ignore
74//! use macroforge_ts_syn::{parse_ts_macro_input, DeriveInput, MacroResult, TsStream};
75//!
76//! // This function signature shows how derive macros receive input
77//! pub fn my_macro(mut input: TsStream) -> MacroResult {
78//!     let input = parse_ts_macro_input!(input as DeriveInput);
79//!
80//!     // Check for a specific decorator
81//!     for attr in &input.attrs {
82//!         if attr.name() == "serde" {
83//!             let _args = attr.args(); // e.g., "rename = \"user\""
84//!             // Parse and handle decorator arguments
85//!         }
86//!     }
87//!
88//!     // Or check field-level decorators
89//!     if let Some(class) = input.as_class() {
90//!         for field in class.fields() {
91//!             for dec in &field.decorators {
92//!                 if dec.name == "serde" {
93//!                     // Handle field-level serde options
94//!                 }
95//!             }
96//!         }
97//!     }
98//!
99//!     MacroResult::default()
100//! }
101//! ```
102//!
103//! ## Span Information
104//!
105//! [`DeriveInput`] provides several useful spans for code generation:
106//!
107//! - [`DeriveInput::decorator_span()`] - The span of the `@derive(...)` decorator
108//! - [`DeriveInput::target_span()`] - The span of the entire type definition
109//! - [`DeriveInput::body_span()`] - The span inside `{ }` for inserting members
110//! - [`DeriveInput::error_span()`] - Best span for error reporting
111
112use crate::abi::{
113    ClassIR, DecoratorIR, EnumIR, EnumVariantIR, FieldIR, InterfaceFieldIR, InterfaceIR,
114    InterfaceMethodIR, MacroContextIR, MethodSigIR, SpanIR, TargetIR, TypeAliasIR, TypeBody,
115    TypeMember,
116};
117
118use crate::TsSynError;
119
120#[cfg(feature = "swc")]
121use crate::TsStream;
122
123/// The input to a derive macro, analogous to `syn::DeriveInput`.
124///
125/// This is the primary entry point for derive macros. It provides a unified
126/// representation of all TypeScript types that can have derive macros applied:
127/// classes, enums, interfaces, and type aliases.
128///
129/// # Creating a DeriveInput
130///
131/// Use the `parse_ts_macro_input!` macro to parse a [`TsStream`] into
132/// a `DeriveInput`:
133///
134/// ```rust,ignore
135/// use macroforge_ts_syn::{parse_ts_macro_input, DeriveInput, TsStream};
136///
137/// // This requires a TsStream with macro context
138/// fn example(mut stream: TsStream) {
139///     let _input = parse_ts_macro_input!(stream as DeriveInput);
140/// }
141/// ```
142///
143/// Or create one directly from a [`MacroContextIR`]:
144///
145/// ```rust,no_run
146/// use macroforge_ts_syn::{DeriveInput, MacroContextIR, TsSynError};
147///
148/// fn example(ctx: MacroContextIR) -> Result<(), TsSynError> {
149///     let _input = DeriveInput::from_context(ctx)?;
150///     Ok(())
151/// }
152/// ```
153///
154/// # Accessing Type Data
155///
156/// Use the `data` field or convenience methods to access type-specific information:
157///
158/// ```rust,no_run
159/// use macroforge_ts_syn::{DeriveInput, Data};
160///
161/// fn example(input: DeriveInput) {
162///     // Using pattern matching
163///     match &input.data {
164///         Data::Class(_class) => { /* ... */ }
165///         Data::Enum(_enum_) => { /* ... */ }
166///         Data::Interface(_iface) => { /* ... */ }
167///         Data::TypeAlias(_alias) => { /* ... */ }
168///     }
169///
170///     // Using convenience methods
171///     if let Some(class) = input.as_class() {
172///         for _field in class.fields() {
173///             // ...
174///         }
175///     }
176/// }
177/// ```
178///
179/// # Fields
180///
181/// - `ident` - The name of the type as an [`Ident`]
182/// - `span` - The source span of the entire type definition
183/// - `attrs` - Decorators on the type (excluding `@derive`)
184/// - `data` - The type-specific data (class fields, enum variants, etc.)
185/// - `context` - The full macro context with additional metadata
186#[derive(Debug, Clone)]
187pub struct DeriveInput {
188    /// The name of the type (class or enum name)
189    pub ident: Ident,
190
191    /// The span of the entire type definition
192    pub span: SpanIR,
193
194    /// Decorators on this type (excluding the derive decorator itself)
195    pub attrs: Vec<Attribute>,
196
197    /// The data within the type (class fields/methods or enum variants)
198    pub data: Data,
199
200    /// The macro context, providing spans and other metadata
201    pub context: MacroContextIR,
202}
203
204/// A simple identifier with span information, analogous to `syn::Ident`.
205///
206/// Represents a TypeScript identifier (type name, field name, etc.) along
207/// with its source location. The identifier preserves the exact text from
208/// the source code.
209///
210/// # Example
211///
212/// ```rust
213/// use macroforge_ts_syn::{Ident, SpanIR};
214///
215/// let ident = Ident::new("MyClass", SpanIR::new(0, 7));
216/// assert_eq!(ident.as_str(), "MyClass");
217/// assert_eq!(format!("{}", ident), "MyClass");
218/// ```
219///
220/// # Converting to String
221///
222/// `Ident` implements `Display`, `AsRef<str>`, and provides `as_str()`:
223///
224/// ```rust
225/// use macroforge_ts_syn::{Ident, SpanIR};
226///
227/// let ident = Ident::new("MyClass", SpanIR::new(0, 7));
228/// let name: &str = ident.as_str();
229/// let name2: &str = ident.as_ref();
230/// let name3: String = ident.to_string();
231/// assert_eq!(name, "MyClass");
232/// assert_eq!(name2, "MyClass");
233/// assert_eq!(name3, "MyClass");
234/// ```
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct Ident {
237    name: String,
238    span: SpanIR,
239}
240
241impl Ident {
242    /// Create a new identifier
243    pub fn new(name: impl Into<String>, span: SpanIR) -> Self {
244        Self {
245            name: name.into(),
246            span,
247        }
248    }
249
250    /// Get the identifier as a string slice
251    pub fn as_str(&self) -> &str {
252        &self.name
253    }
254
255    /// Get the span of this identifier
256    pub fn span(&self) -> SpanIR {
257        self.span
258    }
259}
260
261impl std::fmt::Display for Ident {
262    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263        write!(f, "{}", self.name)
264    }
265}
266
267impl AsRef<str> for Ident {
268    fn as_ref(&self) -> &str {
269        &self.name
270    }
271}
272
273/// An attribute (decorator), analogous to `syn::Attribute`.
274///
275/// Wraps a [`DecoratorIR`] and provides convenient access to decorator
276/// information. Attributes can come from either TypeScript decorators
277/// (`@Decorator()`) or JSDoc comments (`/** @decorator(...) */`).
278///
279/// # Example
280///
281/// ```rust,no_run
282/// use macroforge_ts_syn::DeriveInput;
283///
284/// fn example(input: DeriveInput) {
285///     for attr in &input.attrs {
286///         match attr.name() {
287///             "serde" => {
288///                 // Parse serde options from attr.args()
289///                 let _args = attr.args(); // e.g., "rename = \"user_id\""
290///             }
291///             "validate" => {
292///                 // Handle validation decorator
293///             }
294///             _ => {}
295///         }
296///     }
297/// }
298/// ```
299///
300/// # Note
301///
302/// The `@derive(...)` decorator itself is automatically filtered out from
303/// the `attrs` list in [`DeriveInput`], since the macro is already being
304/// invoked as a result of that decorator.
305#[derive(Debug, Clone)]
306pub struct Attribute {
307    /// The underlying decorator IR with full details.
308    pub inner: DecoratorIR,
309}
310
311impl Attribute {
312    /// Get the attribute/decorator name
313    pub fn name(&self) -> &str {
314        &self.inner.name
315    }
316
317    /// Get the raw arguments string
318    pub fn args(&self) -> &str {
319        &self.inner.args_src
320    }
321
322    /// Get the span
323    pub fn span(&self) -> SpanIR {
324        self.inner.span
325    }
326}
327
328/// The data within a derive input, analogous to `syn::Data`.
329///
330/// This enum distinguishes between the different kinds of TypeScript types
331/// that can have derive macros applied. Each variant contains the type-specific
332/// information needed for code generation.
333///
334/// # Variants
335///
336/// - [`Data::Class`] - A TypeScript class with fields, methods, and modifiers
337/// - [`Data::Enum`] - A TypeScript enum with variants and values
338/// - [`Data::Interface`] - A TypeScript interface with properties and method signatures
339/// - [`Data::TypeAlias`] - A type alias (union, intersection, object, tuple, or simple)
340///
341/// # Example
342///
343/// ```rust
344/// use macroforge_ts_syn::{DeriveInput, Data};
345///
346/// fn process_input(input: &DeriveInput) {
347///     match &input.data {
348///         Data::Class(class) => {
349///             // Generate methods for a class
350///             let _body_span = class.body_span();
351///             for field in class.fields() {
352///                 // Access field.name, field.ts_type, field.optional, etc.
353///                 let _ = &field.name;
354///             }
355///         }
356///         Data::Enum(enum_) => {
357///             // Generate match arms or utility functions
358///             for variant in enum_.variants() {
359///                 // Access variant.name, variant.value
360///                 let _ = &variant.name;
361///             }
362///         }
363///         Data::Interface(iface) => {
364///             // Generate class implementing the interface
365///             for field in iface.fields() {
366///                 // Access field.name, field.ts_type, field.optional
367///                 let _ = &field.name;
368///             }
369///         }
370///         Data::TypeAlias(alias) => {
371///             // Handle different alias structures
372///             if let Some(_union) = alias.as_union() {
373///                 // Handle union type
374///             } else if let Some(_fields) = alias.as_object() {
375///                 // Handle object type
376///             }
377///         }
378///     }
379/// }
380/// ```
381#[derive(Debug, Clone)]
382pub enum Data {
383    /// A TypeScript class with fields, methods, and optional modifiers.
384    Class(DataClass),
385    /// A TypeScript enum with named variants.
386    Enum(DataEnum),
387    /// A TypeScript interface with property and method signatures.
388    Interface(DataInterface),
389    /// A TypeScript type alias (union, intersection, object, etc.).
390    TypeAlias(DataTypeAlias),
391}
392
393/// Data for a TypeScript class, analogous to `syn::DataStruct`.
394///
395/// Provides access to class fields, methods, and metadata. The underlying
396/// [`ClassIR`] contains the full parsed information including AST nodes
397/// for advanced use cases.
398///
399/// # Example
400///
401/// ```rust
402/// use macroforge_ts_syn::{DeriveInput, Data};
403///
404/// fn process_class(input: &DeriveInput) {
405///     if let Data::Class(class) = &input.data {
406///         // Iterate over fields
407///         for field in class.fields() {
408///             println!("Field: {} ({})", field.name, field.ts_type);
409///             if field.optional {
410///                 println!("  (optional)");
411///             }
412///         }
413///
414///         // Check for specific fields
415///         if let Some(_id_field) = class.field("id") {
416///             // Handle id field specifically
417///         }
418///
419///         // Access methods
420///         for method in class.methods() {
421///             println!("Method: {}({})", method.name, method.params_src);
422///         }
423///
424///         // Get the body span for inserting generated code
425///         let _body = class.body_span();
426///     }
427/// }
428/// ```
429#[derive(Debug, Clone)]
430pub struct DataClass {
431    /// The underlying class IR with full details.
432    pub inner: ClassIR,
433}
434
435impl DataClass {
436    /// Get the fields of the class
437    pub fn fields(&self) -> &[FieldIR] {
438        &self.inner.fields
439    }
440
441    /// Get the methods of the class
442    pub fn methods(&self) -> &[MethodSigIR] {
443        &self.inner.methods
444    }
445
446    /// Get the class body span (for inserting code)
447    pub fn body_span(&self) -> SpanIR {
448        self.inner.body_span
449    }
450
451    /// Check if the class is abstract
452    pub fn is_abstract(&self) -> bool {
453        self.inner.is_abstract
454    }
455
456    /// Get type parameters
457    pub fn type_params(&self) -> &[String] {
458        &self.inner.type_params
459    }
460
461    /// Get heritage clauses (extends/implements)
462    pub fn heritage(&self) -> &[String] {
463        &self.inner.heritage
464    }
465
466    /// Iterate over field names
467    pub fn field_names(&self) -> impl Iterator<Item = &str> {
468        self.inner.fields.iter().map(|f| f.name.as_str())
469    }
470
471    /// Get a field by name
472    pub fn field(&self, name: &str) -> Option<&FieldIR> {
473        self.inner.fields.iter().find(|f| f.name == name)
474    }
475
476    /// Get a method by name
477    pub fn method(&self, name: &str) -> Option<&MethodSigIR> {
478        self.inner.methods.iter().find(|m| m.name == name)
479    }
480}
481
482/// Data for a TypeScript enum, analogous to `syn::DataEnum`.
483///
484/// Provides access to enum variants and their values. TypeScript enums
485/// support string, numeric, auto-incremented, and expression values.
486///
487/// # Example
488///
489/// ```rust
490/// use macroforge_ts_syn::{DeriveInput, Data, EnumValue};
491///
492/// fn process_enum(input: &DeriveInput) {
493///     if let Data::Enum(enum_) = &input.data {
494///         // Iterate over variants
495///         for variant in enum_.variants() {
496///             println!("Variant: {}", variant.name);
497///             match &variant.value {
498///                 EnumValue::String(s) => println!("  = \"{}\"", s),
499///                 EnumValue::Number(n) => println!("  = {}", n),
500///                 EnumValue::Auto => println!("  (auto)"),
501///                 EnumValue::Expr(e) => println!("  = {}", e),
502///             }
503///         }
504///
505///         // Get specific variant
506///         if let Some(_active) = enum_.variant("Active") {
507///             // Handle Active variant
508///         }
509///
510///         // Get all variant names
511///         let _names: Vec<_> = enum_.variant_names().collect();
512///     }
513/// }
514/// ```
515#[derive(Debug, Clone)]
516pub struct DataEnum {
517    /// The underlying enum IR with full details.
518    pub inner: EnumIR,
519}
520
521impl DataEnum {
522    /// Get the variants of the enum
523    pub fn variants(&self) -> &[EnumVariantIR] {
524        &self.inner.variants
525    }
526
527    /// Iterate over variant names
528    pub fn variant_names(&self) -> impl Iterator<Item = &str> {
529        self.inner.variants.iter().map(|v| v.name.as_str())
530    }
531
532    /// Get a variant by name
533    pub fn variant(&self, name: &str) -> Option<&EnumVariantIR> {
534        self.inner.variants.iter().find(|v| v.name == name)
535    }
536}
537
538/// Data for a TypeScript interface.
539///
540/// Provides access to interface properties and method signatures. Interfaces
541/// are similar to classes but define only the shape of data without
542/// implementation details.
543///
544/// # Example
545///
546/// ```rust
547/// use macroforge_ts_syn::{DeriveInput, Data};
548///
549/// fn process_interface(input: &DeriveInput) {
550///     if let Data::Interface(iface) = &input.data {
551///         // Iterate over properties
552///         for field in iface.fields() {
553///             let opt = if field.optional { "?" } else { "" };
554///             let ro = if field.readonly { "readonly " } else { "" };
555///             println!("{}{}{}: {}", ro, field.name, opt, field.ts_type);
556///         }
557///
558///         // Access method signatures
559///         for method in iface.methods() {
560///             println!("{}({}): {}", method.name, method.params_src, method.return_type_src);
561///         }
562///
563///         // Get body span for inserting code (useful for generating companion class)
564///         let _body = iface.body_span();
565///     }
566/// }
567/// ```
568///
569/// # Note
570///
571/// Unlike classes, interfaces cannot contain method implementations. Macros
572/// targeting interfaces typically generate companion classes or utility
573/// functions rather than modifying the interface directly.
574#[derive(Debug, Clone)]
575pub struct DataInterface {
576    /// The underlying interface IR with full details.
577    pub inner: InterfaceIR,
578}
579
580impl DataInterface {
581    /// Get the fields of the interface
582    pub fn fields(&self) -> &[InterfaceFieldIR] {
583        &self.inner.fields
584    }
585
586    /// Get the methods of the interface
587    pub fn methods(&self) -> &[InterfaceMethodIR] {
588        &self.inner.methods
589    }
590
591    /// Get the interface body span (for inserting code)
592    pub fn body_span(&self) -> SpanIR {
593        self.inner.body_span
594    }
595
596    /// Get type parameters
597    pub fn type_params(&self) -> &[String] {
598        &self.inner.type_params
599    }
600
601    /// Get heritage clauses (extends)
602    pub fn heritage(&self) -> &[String] {
603        &self.inner.heritage
604    }
605
606    /// Iterate over field names
607    pub fn field_names(&self) -> impl Iterator<Item = &str> {
608        self.inner.fields.iter().map(|f| f.name.as_str())
609    }
610
611    /// Get a field by name
612    pub fn field(&self, name: &str) -> Option<&InterfaceFieldIR> {
613        self.inner.fields.iter().find(|f| f.name == name)
614    }
615
616    /// Get a method by name
617    pub fn method(&self, name: &str) -> Option<&InterfaceMethodIR> {
618        self.inner.methods.iter().find(|m| m.name == name)
619    }
620}
621
622/// Data for a TypeScript type alias.
623///
624/// Type aliases can represent various type structures:
625/// - Union types: `type Status = "active" | "inactive"`
626/// - Intersection types: `type Combined = A & B`
627/// - Object types: `type Point = { x: number; y: number }`
628/// - Tuple types: `type Pair = [string, number]`
629/// - Simple aliases: `type ID = string`
630///
631/// # Example
632///
633/// ```rust
634/// use macroforge_ts_syn::{DeriveInput, Data};
635///
636/// fn process_type_alias(input: &DeriveInput) {
637///     if let Data::TypeAlias(alias) = &input.data {
638///         // Check the type structure
639///         if let Some(union) = alias.as_union() {
640///             println!("Union with {} members", union.len());
641///             for member in union {
642///                 // Each member may have decorators like @default
643///                 if member.has_decorator("default") {
644///                     println!("  Default: {:?}", member.kind);
645///                 }
646///             }
647///         } else if let Some(fields) = alias.as_object() {
648///             println!("Object type with {} fields", fields.len());
649///             for field in fields {
650///                 println!("  {}: {}", field.name, field.ts_type);
651///             }
652///         } else if alias.is_tuple() {
653///             let elements = alias.as_tuple().unwrap();
654///             println!("Tuple: [{}]", elements.join(", "));
655///         } else if let Some(aliased) = alias.as_alias() {
656///             println!("Simple alias to: {}", aliased);
657///         }
658///
659///         // Access type parameters
660///         let params = alias.type_params();
661///         if !params.is_empty() {
662///             println!("Generic: <{}>", params.join(", "));
663///         }
664///     }
665/// }
666/// ```
667#[derive(Debug, Clone)]
668pub struct DataTypeAlias {
669    /// The underlying type alias IR with full details.
670    pub inner: TypeAliasIR,
671}
672
673impl DataTypeAlias {
674    /// Get the type body
675    pub fn body(&self) -> &TypeBody {
676        &self.inner.body
677    }
678
679    /// Get type parameters
680    pub fn type_params(&self) -> &[String] {
681        &self.inner.type_params
682    }
683
684    /// Check if this is a union type
685    pub fn is_union(&self) -> bool {
686        self.inner.body.is_union()
687    }
688
689    /// Check if this is an intersection type
690    pub fn is_intersection(&self) -> bool {
691        self.inner.body.is_intersection()
692    }
693
694    /// Check if this is an object type
695    pub fn is_object(&self) -> bool {
696        self.inner.body.is_object()
697    }
698
699    /// Check if this is a tuple type
700    pub fn is_tuple(&self) -> bool {
701        self.inner.body.is_tuple()
702    }
703
704    /// Check if this is a simple alias
705    pub fn is_alias(&self) -> bool {
706        self.inner.body.is_alias()
707    }
708
709    /// Get union members if this is a union type
710    pub fn as_union(&self) -> Option<&[TypeMember]> {
711        self.inner.body.as_union()
712    }
713
714    /// Get intersection members if this is an intersection type
715    pub fn as_intersection(&self) -> Option<&[TypeMember]> {
716        self.inner.body.as_intersection()
717    }
718
719    /// Get object fields if this is an object type
720    pub fn as_object(&self) -> Option<&[InterfaceFieldIR]> {
721        self.inner.body.as_object()
722    }
723
724    /// Get tuple elements if this is a tuple type
725    pub fn as_tuple(&self) -> Option<&[String]> {
726        self.inner.body.as_tuple()
727    }
728
729    /// Get aliased type if this is a simple alias
730    pub fn as_alias(&self) -> Option<&str> {
731        self.inner.body.as_alias()
732    }
733}
734
735impl DeriveInput {
736    /// Create a DeriveInput from a MacroContextIR
737    pub fn from_context(ctx: MacroContextIR) -> Result<Self, TsSynError> {
738        let (ident, span, attrs, data) = match &ctx.target {
739            TargetIR::Class(class) => {
740                let ident = Ident::new(&class.name, class.span);
741                let attrs = class
742                    .decorators
743                    .iter()
744                    .filter(|d| d.name != "Derive") // Filter out the derive decorator itself
745                    .cloned()
746                    .map(|d| Attribute { inner: d })
747                    .collect();
748                let data = Data::Class(DataClass {
749                    inner: class.clone(),
750                });
751                (ident, class.span, attrs, data)
752            }
753            TargetIR::Enum(enum_) => {
754                let ident = Ident::new(&enum_.name, enum_.span);
755                let attrs = enum_
756                    .decorators
757                    .iter()
758                    .filter(|d| d.name != "Derive")
759                    .cloned()
760                    .map(|d| Attribute { inner: d })
761                    .collect();
762                let data = Data::Enum(DataEnum {
763                    inner: enum_.clone(),
764                });
765                (ident, enum_.span, attrs, data)
766            }
767            TargetIR::Interface(interface) => {
768                let ident = Ident::new(&interface.name, interface.span);
769                let attrs = interface
770                    .decorators
771                    .iter()
772                    .filter(|d| d.name != "Derive")
773                    .cloned()
774                    .map(|d| Attribute { inner: d })
775                    .collect();
776                let data = Data::Interface(DataInterface {
777                    inner: interface.clone(),
778                });
779                (ident, interface.span, attrs, data)
780            }
781            TargetIR::TypeAlias(type_alias) => {
782                let ident = Ident::new(&type_alias.name, type_alias.span);
783                let attrs = type_alias
784                    .decorators
785                    .iter()
786                    .filter(|d| d.name != "Derive")
787                    .cloned()
788                    .map(|d| Attribute { inner: d })
789                    .collect();
790                let data = Data::TypeAlias(DataTypeAlias {
791                    inner: type_alias.clone(),
792                });
793                (ident, type_alias.span, attrs, data)
794            }
795            TargetIR::Function => {
796                return Err(TsSynError::Unsupported(
797                    "Function derive macros not yet supported".into(),
798                ));
799            }
800            TargetIR::Other => {
801                return Err(TsSynError::Unsupported(
802                    "Unknown target type for derive macro".into(),
803                ));
804            }
805        };
806
807        Ok(Self {
808            ident,
809            span,
810            attrs,
811            data,
812            context: ctx,
813        })
814    }
815
816    /// Get the name of the type as a string
817    pub fn name(&self) -> &str {
818        self.ident.as_str()
819    }
820
821    /// Get the class data, if this is a class
822    pub fn as_class(&self) -> Option<&DataClass> {
823        match &self.data {
824            Data::Class(c) => Some(c),
825            _ => None,
826        }
827    }
828
829    /// Get the enum data, if this is an enum
830    pub fn as_enum(&self) -> Option<&DataEnum> {
831        match &self.data {
832            Data::Enum(e) => Some(e),
833            _ => None,
834        }
835    }
836
837    /// Get the interface data, if this is an interface
838    pub fn as_interface(&self) -> Option<&DataInterface> {
839        match &self.data {
840            Data::Interface(i) => Some(i),
841            _ => None,
842        }
843    }
844
845    /// Get the type alias data, if this is a type alias
846    pub fn as_type_alias(&self) -> Option<&DataTypeAlias> {
847        match &self.data {
848            Data::TypeAlias(t) => Some(t),
849            _ => None,
850        }
851    }
852
853    /// Get the decorator span (for deletion/replacement)
854    pub fn decorator_span(&self) -> SpanIR {
855        self.context.decorator_span
856    }
857
858    /// Get the macro name span (just the macro name within the decorator)
859    /// Returns None if not available
860    pub fn macro_name_span(&self) -> Option<SpanIR> {
861        self.context.macro_name_span
862    }
863
864    /// Get the best span for error reporting - prefers macro_name_span if available,
865    /// falls back to decorator_span
866    pub fn error_span(&self) -> SpanIR {
867        self.context.error_span()
868    }
869
870    /// Get the target span (for inserting after)
871    pub fn target_span(&self) -> SpanIR {
872        self.context.target_span
873    }
874
875    /// Get the class or interface body span for inserting type signatures
876    /// Returns None if this is an enum or type alias
877    pub fn body_span(&self) -> Option<SpanIR> {
878        match &self.data {
879            Data::Class(c) => Some(c.body_span()),
880            Data::Interface(i) => Some(i.body_span()),
881            Data::Enum(_) => None,
882            Data::TypeAlias(_) => None,
883        }
884    }
885}
886
887#[cfg(feature = "swc")]
888impl crate::ParseTs for DeriveInput {
889    fn parse(input: &mut TsStream) -> Result<Self, TsSynError> {
890        let ctx = input
891            .context()
892            .ok_or_else(|| TsSynError::Parse("No macro context available".into()))?
893            .clone();
894
895        Self::from_context(ctx)
896    }
897}
898
899/// Parse a `TsStream` into a `DeriveInput`, returning early with an error `MacroResult` on failure.
900///
901/// This macro is analogous to `syn::parse_macro_input!` and provides ergonomic error handling
902/// for derive macros.
903///
904/// # Example
905/// ```ignore
906/// use ts_syn::{parse_ts_macro_input, DeriveInput};
907/// use ts_syn::MacroResult;
908///
909/// #[ts_macro_derive(MyMacro)]
910/// pub fn my_macro(mut input: TsStream) -> MacroResult {
911///     let input = parse_ts_macro_input!(input as DeriveInput);
912///
913///     // input is now a DeriveInput
914///     let name = input.name();
915///     // ...
916/// }
917/// ```
918///
919/// # Variants
920/// - `parse_ts_macro_input!(stream as DeriveInput)` - Parse as DeriveInput
921/// - `parse_ts_macro_input!(stream)` - Same as above (DeriveInput is the default)
922#[macro_export]
923macro_rules! parse_ts_macro_input {
924    ($input:ident as $ty:ty) => {
925        match <$ty as $crate::ParseTs>::parse(&mut $input) {
926            Ok(parsed) => parsed,
927            Err(e) => {
928                return Err($crate::MacroforgeError::new_global(format!(
929                    "Failed to parse input: {}",
930                    e
931                )));
932            }
933        }
934    };
935    ($input:ident) => {
936        $crate::parse_ts_macro_input!($input as $crate::DeriveInput)
937    };
938}
939
940#[cfg(test)]
941mod tests {
942    use super::*;
943    use crate::abi::{MacroKind, SpanIR};
944
945    fn make_test_class_context() -> MacroContextIR {
946        MacroContextIR {
947            abi_version: 1,
948            macro_kind: MacroKind::Derive,
949            macro_name: "Debug".into(),
950            module_path: "@test/macro".into(),
951            decorator_span: SpanIR::new(0, 10),
952            macro_name_span: None,
953            target_span: SpanIR::new(11, 100),
954            file_name: "test.ts".into(),
955            target: TargetIR::Class(ClassIR {
956                name: "User".into(),
957                span: SpanIR::new(11, 100),
958                body_span: SpanIR::new(20, 99),
959                is_abstract: false,
960                type_params: vec![],
961                heritage: vec![],
962                decorators: vec![],
963                fields: vec![
964                    FieldIR {
965                        name: "id".into(),
966                        span: SpanIR::new(25, 35),
967                        ts_type: "number".into(),
968                        type_ann: None,
969                        optional: false,
970                        readonly: false,
971                        visibility: crate::abi::Visibility::Public,
972                        decorators: vec![],
973                        prop_ast: None,
974                    },
975                    FieldIR {
976                        name: "name".into(),
977                        span: SpanIR::new(40, 55),
978                        ts_type: "string".into(),
979                        type_ann: None,
980                        optional: false,
981                        readonly: false,
982                        visibility: crate::abi::Visibility::Public,
983                        decorators: vec![],
984                        prop_ast: None,
985                    },
986                ],
987                methods: vec![],
988                decorators_ast: vec![],
989                members: vec![],
990            }),
991            target_source: "class User { id: number; name: string; }".into(),
992        }
993    }
994
995    #[test]
996    fn test_derive_input_from_class_context() {
997        let ctx = make_test_class_context();
998        let input = DeriveInput::from_context(ctx).expect("should parse");
999
1000        assert_eq!(input.name(), "User");
1001        assert!(input.as_class().is_some());
1002        assert!(input.as_enum().is_none());
1003
1004        let class = input.as_class().unwrap();
1005        assert_eq!(class.fields().len(), 2);
1006        assert!(class.field("id").is_some());
1007        assert!(class.field("name").is_some());
1008        assert!(class.field("nonexistent").is_none());
1009
1010        let field_names: Vec<_> = class.field_names().collect();
1011        assert_eq!(field_names, vec!["id", "name"]);
1012    }
1013
1014    #[test]
1015    fn test_derive_input_from_enum_context() {
1016        let ctx = MacroContextIR {
1017            abi_version: 1,
1018            macro_kind: MacroKind::Derive,
1019            macro_name: "Debug".into(),
1020            module_path: "@test/macro".into(),
1021            decorator_span: SpanIR::new(0, 10),
1022            macro_name_span: None,
1023            target_span: SpanIR::new(11, 100),
1024            file_name: "test.ts".into(),
1025            target: TargetIR::Enum(EnumIR {
1026                name: "Status".into(),
1027                span: SpanIR::new(11, 100),
1028                body_span: SpanIR::new(18, 99),
1029                decorators: vec![],
1030                variants: vec![
1031                    EnumVariantIR {
1032                        name: "Active".into(),
1033                        span: SpanIR::new(20, 30),
1034                        value: crate::abi::EnumValue::Auto,
1035                        decorators: vec![],
1036                    },
1037                    EnumVariantIR {
1038                        name: "Inactive".into(),
1039                        span: SpanIR::new(35, 45),
1040                        value: crate::abi::EnumValue::Auto,
1041                        decorators: vec![],
1042                    },
1043                ],
1044                is_const: false,
1045            }),
1046            target_source: "enum Status { Active, Inactive }".into(),
1047        };
1048
1049        let input = DeriveInput::from_context(ctx).expect("should parse");
1050
1051        assert_eq!(input.name(), "Status");
1052        assert!(input.as_enum().is_some());
1053        assert!(input.as_class().is_none());
1054
1055        let enum_ = input.as_enum().unwrap();
1056        assert_eq!(enum_.variants().len(), 2);
1057        assert!(enum_.variant("Active").is_some());
1058        assert!(enum_.variant("Inactive").is_some());
1059
1060        let variant_names: Vec<_> = enum_.variant_names().collect();
1061        assert_eq!(variant_names, vec!["Active", "Inactive"]);
1062    }
1063
1064    #[test]
1065    fn test_ident_display() {
1066        let ident = Ident::new("MyClass", SpanIR::new(0, 7));
1067        assert_eq!(format!("{}", ident), "MyClass");
1068        assert_eq!(ident.as_str(), "MyClass");
1069        assert_eq!(ident.as_ref(), "MyClass");
1070    }
1071}