tsify-macros 0.4.3

Macros for tsify
Documentation
use std::fmt::Display;
use std::ops::Deref;

use crate::typescript::{TsType, TsTypeElement, TsTypeLit};

#[derive(Clone)]
pub struct TsTypeAliasDecl {
    pub id: String,
    pub export: bool,
    pub type_params: Vec<String>,
    pub type_ann: TsType,
}

impl Display for TsTypeAliasDecl {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let right = if self.type_params.is_empty() {
            self.id.clone()
        } else {
            let type_params = self.type_params.join(", ");
            format!("{}<{}>", self.id, type_params)
        };

        if self.export {
            write!(f, "export ")?;
        }
        write!(f, "type {} = {};", right, self.type_ann)
    }
}

pub struct TsInterfaceDecl {
    pub id: String,
    pub type_params: Vec<String>,
    pub extends: Vec<TsType>,
    pub body: Vec<TsTypeElement>,
}

impl Display for TsInterfaceDecl {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "export interface {}", self.id)?;

        if !self.type_params.is_empty() {
            let type_params = self.type_params.join(", ");
            write!(f, "<{type_params}>")?;
        }

        if !self.extends.is_empty() {
            let extends = self
                .extends
                .iter()
                .map(|ty| ty.to_string())
                .collect::<Vec<_>>()
                .join(", ");

            write!(f, " extends {extends}")?;
        }

        if self.body.is_empty() {
            write!(f, " {{}}")
        } else {
            let members = self
                .body
                .iter()
                .map(|elem| format!("\n    {elem};"))
                .collect::<Vec<_>>()
                .join("");

            write!(f, " {{{members}\n}}")
        }
    }
}

pub struct TsEnumDecl {
    pub id: String,
    pub type_params: Vec<String>,
    pub members: Vec<TsTypeAliasDecl>,
    pub namespace: bool,
}

const ALPHABET_UPPER: [char; 26] = [
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S',
    'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
];

fn tparam(i: usize) -> String {
    let mut s = String::new();
    let mut i = i;
    loop {
        s.push(ALPHABET_UPPER[i % ALPHABET_UPPER.len()]);
        if i < ALPHABET_UPPER.len() {
            return s;
        }
        i /= ALPHABET_UPPER.len();
    }
}

impl TsEnumDecl {
    fn replace_type_params(ts_type: TsType, type_args: &mut Vec<String>) -> TsType {
        match ts_type {
            TsType::Ref { name, type_params } => TsType::Ref {
                name,
                type_params: type_params
                    .iter()
                    .map(|_| {
                        let name = tparam(type_args.len());
                        type_args.push(name.clone());
                        TsType::Ref {
                            name,
                            type_params: Vec::new(),
                        }
                    })
                    .collect(),
            },
            TsType::Array(t) => TsType::Array(Box::new(TsEnumDecl::replace_type_params(
                t.deref().clone(),
                type_args,
            ))),
            TsType::Tuple(tv) => TsType::Tuple(
                tv.iter()
                    .map(|t| TsEnumDecl::replace_type_params(t.clone(), type_args))
                    .collect(),
            ),
            TsType::Option(t) => TsType::Option(Box::new(TsEnumDecl::replace_type_params(
                t.deref().clone(),
                type_args,
            ))),
            TsType::Fn { params, type_ann } => TsType::Fn {
                params: params
                    .iter()
                    .map(|t| TsEnumDecl::replace_type_params(t.clone(), type_args))
                    .collect(),
                type_ann: Box::new(TsEnumDecl::replace_type_params(
                    type_ann.deref().clone(),
                    type_args,
                )),
            },
            TsType::TypeLit(lit) => TsType::TypeLit(TsTypeLit {
                members: lit
                    .members
                    .iter()
                    .map(|t| TsTypeElement {
                        key: t.key.clone(),
                        optional: t.optional,
                        type_ann: TsEnumDecl::replace_type_params(t.type_ann.clone(), type_args),
                    })
                    .collect(),
            }),
            TsType::Intersection(tv) => TsType::Intersection(
                tv.iter()
                    .map(|t| TsEnumDecl::replace_type_params(t.clone(), type_args))
                    .collect(),
            ),
            TsType::Union(tv) => TsType::Union(
                tv.iter()
                    .map(|t| TsEnumDecl::replace_type_params(t.clone(), type_args))
                    .collect(),
            ),
            _ => ts_type,
        }
    }
}

impl Display for TsEnumDecl {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.namespace {
            let mut type_refs = self
                .members
                .iter()
                .flat_map(|type_alias| {
                    let mut type_refs = Vec::new();
                    type_alias.type_ann.type_refs(&mut type_refs);

                    type_refs
                        .iter()
                        .filter(|(name, _)| !self.type_params.contains(name))
                        .map(|(name, type_args)| {
                            let mut type_refs = Vec::new();
                            let ts_type = TsEnumDecl::replace_type_params(
                                TsType::Ref {
                                    name: name.clone(),
                                    type_params: type_args.clone(),
                                },
                                &mut type_refs,
                            );

                            TsTypeAliasDecl {
                                id: format!("__{}{}", self.id, name),
                                export: false,
                                type_params: type_refs,
                                type_ann: ts_type,
                            }
                        })
                        .collect::<Vec<_>>()
                })
                .collect::<Vec<_>>();
            type_refs.sort_by_key(|type_ref| type_ref.id.clone());
            type_refs.dedup_by_key(|type_ref| type_ref.id.clone());
            for type_ref in type_refs {
                writeln!(f, "{}", type_ref)?;
            }
            write!(f, "declare namespace {}", self.id)?;

            if self.members.is_empty() {
                write!(f, " {{}}")?;
            } else {
                let prefix = format!("__{}", self.id);
                let members = self
                    .members
                    .iter()
                    .map(|elem| TsTypeAliasDecl {
                        id: elem.id.clone(),
                        export: true,
                        type_params: elem.type_params.clone(),
                        type_ann: elem
                            .type_ann
                            .clone()
                            .prefix_type_refs(&prefix, &self.type_params),
                    })
                    .map(|elem| format!("\n    {elem}"))
                    .collect::<Vec<_>>()
                    .join("");

                write!(f, " {{{members}\n}}")?;
            }

            write!(f, "\n\n")?;
        }

        TsTypeAliasDecl {
            id: self.id.clone(),
            export: true,
            type_params: self.type_params.clone(),
            type_ann: TsType::Union(
                self.members
                    .iter()
                    .map(|member| member.type_ann.clone())
                    .collect(),
            ),
        }
        .fmt(f)
    }
}

#[allow(clippy::enum_variant_names)]
pub enum Decl {
    TsTypeAlias(TsTypeAliasDecl),
    TsInterface(TsInterfaceDecl),
    TsEnum(TsEnumDecl),
}

impl Decl {
    pub fn id(&self) -> &String {
        match self {
            Decl::TsTypeAlias(decl) => &decl.id,
            Decl::TsInterface(decl) => &decl.id,
            Decl::TsEnum(decl) => &decl.id,
        }
    }
}

impl Display for Decl {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Decl::TsTypeAlias(decl) => decl.fmt(f),
            Decl::TsInterface(decl) => decl.fmt(f),
            Decl::TsEnum(decl) => decl.fmt(f),
        }
    }
}