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}