use crate::abi::{
ClassIR, DecoratorIR, EnumIR, EnumVariantIR, FieldIR, InterfaceFieldIR, InterfaceIR,
InterfaceMethodIR, MacroContextIR, MethodSigIR, SpanIR, TargetIR, TypeAliasIR, TypeBody,
TypeMember,
};
use crate::TsSynError;
#[cfg(feature = "swc")]
use crate::TsStream;
#[derive(Debug, Clone)]
pub struct DeriveInput {
pub ident: Ident,
pub span: SpanIR,
pub attrs: Vec<Attribute>,
pub data: Data,
pub context: MacroContextIR,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ident {
name: String,
span: SpanIR,
}
impl Ident {
pub fn new(name: impl Into<String>, span: SpanIR) -> Self {
Self {
name: name.into(),
span,
}
}
pub fn as_str(&self) -> &str {
&self.name
}
pub fn span(&self) -> SpanIR {
self.span
}
}
impl std::fmt::Display for Ident {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
impl AsRef<str> for Ident {
fn as_ref(&self) -> &str {
&self.name
}
}
#[derive(Debug, Clone)]
pub struct Attribute {
pub inner: DecoratorIR,
}
impl Attribute {
pub fn name(&self) -> &str {
&self.inner.name
}
pub fn args(&self) -> &str {
&self.inner.args_src
}
pub fn span(&self) -> SpanIR {
self.inner.span
}
}
#[derive(Debug, Clone)]
pub enum Data {
Class(DataClass),
Enum(DataEnum),
Interface(DataInterface),
TypeAlias(DataTypeAlias),
}
#[derive(Debug, Clone)]
pub struct DataClass {
pub inner: ClassIR,
}
impl DataClass {
pub fn fields(&self) -> &[FieldIR] {
&self.inner.fields
}
pub fn methods(&self) -> &[MethodSigIR] {
&self.inner.methods
}
pub fn body_span(&self) -> SpanIR {
self.inner.body_span
}
pub fn is_abstract(&self) -> bool {
self.inner.is_abstract
}
pub fn type_params(&self) -> &[String] {
&self.inner.type_params
}
pub fn heritage(&self) -> &[String] {
&self.inner.heritage
}
pub fn field_names(&self) -> impl Iterator<Item = &str> {
self.inner.fields.iter().map(|f| f.name.as_str())
}
pub fn field(&self, name: &str) -> Option<&FieldIR> {
self.inner.fields.iter().find(|f| f.name == name)
}
pub fn method(&self, name: &str) -> Option<&MethodSigIR> {
self.inner.methods.iter().find(|m| m.name == name)
}
}
#[derive(Debug, Clone)]
pub struct DataEnum {
pub inner: EnumIR,
}
impl DataEnum {
pub fn variants(&self) -> &[EnumVariantIR] {
&self.inner.variants
}
pub fn variant_names(&self) -> impl Iterator<Item = &str> {
self.inner.variants.iter().map(|v| v.name.as_str())
}
pub fn variant(&self, name: &str) -> Option<&EnumVariantIR> {
self.inner.variants.iter().find(|v| v.name == name)
}
}
#[derive(Debug, Clone)]
pub struct DataInterface {
pub inner: InterfaceIR,
}
impl DataInterface {
pub fn fields(&self) -> &[InterfaceFieldIR] {
&self.inner.fields
}
pub fn methods(&self) -> &[InterfaceMethodIR] {
&self.inner.methods
}
pub fn body_span(&self) -> SpanIR {
self.inner.body_span
}
pub fn type_params(&self) -> &[String] {
&self.inner.type_params
}
pub fn heritage(&self) -> &[String] {
&self.inner.heritage
}
pub fn field_names(&self) -> impl Iterator<Item = &str> {
self.inner.fields.iter().map(|f| f.name.as_str())
}
pub fn field(&self, name: &str) -> Option<&InterfaceFieldIR> {
self.inner.fields.iter().find(|f| f.name == name)
}
pub fn method(&self, name: &str) -> Option<&InterfaceMethodIR> {
self.inner.methods.iter().find(|m| m.name == name)
}
}
#[derive(Debug, Clone)]
pub struct DataTypeAlias {
pub inner: TypeAliasIR,
}
impl DataTypeAlias {
pub fn body(&self) -> &TypeBody {
&self.inner.body
}
pub fn type_params(&self) -> &[String] {
&self.inner.type_params
}
pub fn is_union(&self) -> bool {
self.inner.body.is_union()
}
pub fn is_intersection(&self) -> bool {
self.inner.body.is_intersection()
}
pub fn is_object(&self) -> bool {
self.inner.body.is_object()
}
pub fn is_tuple(&self) -> bool {
self.inner.body.is_tuple()
}
pub fn is_alias(&self) -> bool {
self.inner.body.is_alias()
}
pub fn as_union(&self) -> Option<&[TypeMember]> {
self.inner.body.as_union()
}
pub fn as_intersection(&self) -> Option<&[TypeMember]> {
self.inner.body.as_intersection()
}
pub fn as_object(&self) -> Option<&[InterfaceFieldIR]> {
self.inner.body.as_object()
}
pub fn as_tuple(&self) -> Option<&[String]> {
self.inner.body.as_tuple()
}
pub fn as_alias(&self) -> Option<&str> {
self.inner.body.as_alias()
}
}
impl DeriveInput {
pub fn from_context(ctx: MacroContextIR) -> Result<Self, TsSynError> {
let (ident, span, attrs, data) = match &ctx.target {
TargetIR::Class(class) => {
let ident = Ident::new(&class.name, class.span);
let attrs = class
.decorators
.iter()
.filter(|d| d.name != "Derive") .cloned()
.map(|d| Attribute { inner: d })
.collect();
let data = Data::Class(DataClass {
inner: class.clone(),
});
(ident, class.span, attrs, data)
}
TargetIR::Enum(enum_) => {
let ident = Ident::new(&enum_.name, enum_.span);
let attrs = enum_
.decorators
.iter()
.filter(|d| d.name != "Derive")
.cloned()
.map(|d| Attribute { inner: d })
.collect();
let data = Data::Enum(DataEnum {
inner: enum_.clone(),
});
(ident, enum_.span, attrs, data)
}
TargetIR::Interface(interface) => {
let ident = Ident::new(&interface.name, interface.span);
let attrs = interface
.decorators
.iter()
.filter(|d| d.name != "Derive")
.cloned()
.map(|d| Attribute { inner: d })
.collect();
let data = Data::Interface(DataInterface {
inner: interface.clone(),
});
(ident, interface.span, attrs, data)
}
TargetIR::TypeAlias(type_alias) => {
let ident = Ident::new(&type_alias.name, type_alias.span);
let attrs = type_alias
.decorators
.iter()
.filter(|d| d.name != "Derive")
.cloned()
.map(|d| Attribute { inner: d })
.collect();
let data = Data::TypeAlias(DataTypeAlias {
inner: type_alias.clone(),
});
(ident, type_alias.span, attrs, data)
}
TargetIR::Function => {
return Err(TsSynError::Unsupported(
"Function derive macros not yet supported".into(),
));
}
TargetIR::Other => {
return Err(TsSynError::Unsupported(
"Unknown target type for derive macro".into(),
));
}
};
Ok(Self {
ident,
span,
attrs,
data,
context: ctx,
})
}
pub fn name(&self) -> &str {
self.ident.as_str()
}
pub fn as_class(&self) -> Option<&DataClass> {
match &self.data {
Data::Class(c) => Some(c),
_ => None,
}
}
pub fn as_enum(&self) -> Option<&DataEnum> {
match &self.data {
Data::Enum(e) => Some(e),
_ => None,
}
}
pub fn as_interface(&self) -> Option<&DataInterface> {
match &self.data {
Data::Interface(i) => Some(i),
_ => None,
}
}
pub fn as_type_alias(&self) -> Option<&DataTypeAlias> {
match &self.data {
Data::TypeAlias(t) => Some(t),
_ => None,
}
}
pub fn decorator_span(&self) -> SpanIR {
self.context.decorator_span
}
pub fn macro_name_span(&self) -> Option<SpanIR> {
self.context.macro_name_span
}
pub fn error_span(&self) -> SpanIR {
self.context.error_span()
}
pub fn target_span(&self) -> SpanIR {
self.context.target_span
}
pub fn body_span(&self) -> Option<SpanIR> {
match &self.data {
Data::Class(c) => Some(c.body_span()),
Data::Interface(i) => Some(i.body_span()),
Data::Enum(_) => None,
Data::TypeAlias(_) => None,
}
}
}
#[cfg(feature = "swc")]
impl crate::ParseTs for DeriveInput {
fn parse(input: &mut TsStream) -> Result<Self, TsSynError> {
let ctx = input
.context()
.ok_or_else(|| TsSynError::Parse("No macro context available".into()))?
.clone();
Self::from_context(ctx)
}
}
#[macro_export]
macro_rules! parse_ts_macro_input {
($input:ident as $ty:ty) => {
match <$ty as $crate::ParseTs>::parse(&mut $input) {
Ok(parsed) => parsed,
Err(e) => {
return Err($crate::MacroforgeError::new_global(format!(
"Failed to parse input: {}",
e
)));
}
}
};
($input:ident) => {
$crate::parse_ts_macro_input!($input as $crate::DeriveInput)
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::abi::{MacroKind, SpanIR};
fn make_test_class_context() -> MacroContextIR {
MacroContextIR {
abi_version: 1,
macro_kind: MacroKind::Derive,
macro_name: "Debug".into(),
module_path: "@test/macro".into(),
decorator_span: SpanIR::new(0, 10),
macro_name_span: None,
target_span: SpanIR::new(11, 100),
file_name: "test.ts".into(),
target: TargetIR::Class(ClassIR {
name: "User".into(),
span: SpanIR::new(11, 100),
body_span: SpanIR::new(20, 99),
is_abstract: false,
type_params: vec![],
heritage: vec![],
decorators: vec![],
fields: vec![
FieldIR {
name: "id".into(),
span: SpanIR::new(25, 35),
ts_type: "number".into(),
type_ann: None,
optional: false,
readonly: false,
visibility: crate::abi::Visibility::Public,
decorators: vec![],
prop_ast: None,
},
FieldIR {
name: "name".into(),
span: SpanIR::new(40, 55),
ts_type: "string".into(),
type_ann: None,
optional: false,
readonly: false,
visibility: crate::abi::Visibility::Public,
decorators: vec![],
prop_ast: None,
},
],
methods: vec![],
decorators_ast: vec![],
members: vec![],
}),
target_source: "class User { id: number; name: string; }".into(),
import_registry: crate::import_registry::ImportRegistry::new(),
config: None,
type_registry: None,
resolved_fields: None,
}
}
#[test]
fn test_derive_input_from_class_context() {
let ctx = make_test_class_context();
let input = DeriveInput::from_context(ctx).expect("should parse");
assert_eq!(input.name(), "User");
assert!(input.as_class().is_some());
assert!(input.as_enum().is_none());
let class = input.as_class().unwrap();
assert_eq!(class.fields().len(), 2);
assert!(class.field("id").is_some());
assert!(class.field("name").is_some());
assert!(class.field("nonexistent").is_none());
let field_names: Vec<_> = class.field_names().collect();
assert_eq!(field_names, vec!["id", "name"]);
}
#[test]
fn test_derive_input_from_enum_context() {
let ctx = MacroContextIR {
abi_version: 1,
macro_kind: MacroKind::Derive,
macro_name: "Debug".into(),
module_path: "@test/macro".into(),
decorator_span: SpanIR::new(0, 10),
macro_name_span: None,
target_span: SpanIR::new(11, 100),
file_name: "test.ts".into(),
target: TargetIR::Enum(EnumIR {
name: "Status".into(),
span: SpanIR::new(11, 100),
body_span: SpanIR::new(18, 99),
decorators: vec![],
variants: vec![
EnumVariantIR {
name: "Active".into(),
span: SpanIR::new(20, 30),
value: crate::abi::EnumValue::Auto,
decorators: vec![],
},
EnumVariantIR {
name: "Inactive".into(),
span: SpanIR::new(35, 45),
value: crate::abi::EnumValue::Auto,
decorators: vec![],
},
],
is_const: false,
}),
target_source: "enum Status { Active, Inactive }".into(),
import_registry: crate::import_registry::ImportRegistry::new(),
config: None,
type_registry: None,
resolved_fields: None,
};
let input = DeriveInput::from_context(ctx).expect("should parse");
assert_eq!(input.name(), "Status");
assert!(input.as_enum().is_some());
assert!(input.as_class().is_none());
let enum_ = input.as_enum().unwrap();
assert_eq!(enum_.variants().len(), 2);
assert!(enum_.variant("Active").is_some());
assert!(enum_.variant("Inactive").is_some());
let variant_names: Vec<_> = enum_.variant_names().collect();
assert_eq!(variant_names, vec!["Active", "Inactive"]);
}
#[test]
fn test_ident_display() {
let ident = Ident::new("MyClass", SpanIR::new(0, 7));
assert_eq!(format!("{}", ident), "MyClass");
assert_eq!(ident.as_str(), "MyClass");
assert_eq!(ident.as_ref(), "MyClass");
}
}