tslink 0.4.2

Creates typescript definitions based on rust code
Documentation
use crate::{
    config::Config,
    context::Context,
    error::E,
    interpreter::serialize_name,
    nature::{Composite, Nature, OriginType, Primitive, Referred},
};
use syn::{
    punctuated::Punctuated, token::PathSep, FnArg, GenericArgument, Ident, ImplItemFn, ItemFn, Pat,
    PathArguments, PathSegment, ReturnType, Type, TypeTuple,
};

use super::TypeAsString;

pub fn get_fn_return(
    output: &ReturnType,
    context: &Context,
    asyncness: bool,
    cfg: &Config,
) -> Result<Option<Box<Nature>>, E> {
    Ok(match output {
        ReturnType::Default => Some(Box::new(Nature::Composite(Composite::Result(
            OriginType::from(output.clone()),
            None,
            None,
            context.exception_suppression()?,
            asyncness,
        )))),
        ReturnType::Type(_, ty) => {
            let return_ty = Nature::extract(*ty.clone(), context.clone(), cfg)?;
            Some(
                if let Nature::Composite(Composite::Result(a, b, c, d, _)) = return_ty {
                    Box::new(Nature::Composite(Composite::Result(a, b, c, d, asyncness)))
                } else {
                    Box::new(Nature::Composite(Composite::Result(
                        OriginType::from(*ty.clone()),
                        Some(Box::new(return_ty)),
                        None,
                        context.exception_suppression()?,
                        asyncness,
                    )))
                },
            )
        }
    })
}

pub trait Extract<T> {
    fn extract(t: T, context: Context, cfg: &Config) -> Result<Nature, E>;
}

impl Extract<&GenericArgument> for Nature {
    fn extract(arg: &GenericArgument, context: Context, cfg: &Config) -> Result<Nature, E> {
        match arg {
            GenericArgument::Type(ty) => Nature::extract(ty, context, cfg),
            _ => Err(E::NotSupported("GenericArgument".to_owned())),
        }
    }
}

impl Extract<&Ident> for Nature {
    fn extract(ident: &Ident, context: Context, cfg: &Config) -> Result<Nature, E> {
        let origin = ident.to_string();
        Ok(match (origin.as_str(), cfg.int_over_32_as_big_int) {
            ("u8" | "u16" | "u32" | "i8" | "i16" | "i32" | "f16" | "f32" | "f64", true) => {
                Nature::Primitive(Primitive::Number(OriginType::from(ident.clone())))
            }
            ("u64" | "u128" | "i64" | "i128" | "usize" | "isize", true) => {
                Nature::Primitive(Primitive::BigInt(OriginType::from(ident.clone())))
            }
            (
                "u8" | "u16" | "u32" | "u64" | "usize" | "i8" | "i16" | "i32" | "i64" | "isize"
                | "f16" | "f32" | "f64",
                false,
            ) => Nature::Primitive(Primitive::Number(OriginType::from(ident.clone()))),
            ("u128" | "i128", false) => {
                Nature::Primitive(Primitive::BigInt(OriginType::from(ident.clone())))
            }
            ("bool", ..) => Nature::Primitive(Primitive::Boolean(OriginType::from(ident.clone()))),
            ("String" | "str", ..) => {
                Nature::Primitive(Primitive::String(OriginType::from(ident.clone())))
            }
            ("f128", ..) => {
                return Err(E::NotSupported(
                    "Type <f128> doesn't have direct equalent in JavaScript".to_owned(),
                ))
            }
            (a, ..) => {
                let serialized = cfg.overwrite_reftype(serialize_name(a));
                match serialized.as_ref() {
                    "boolean" => {
                        Nature::Primitive(Primitive::Boolean(OriginType::from(ident.clone())))
                    }
                    "string" => {
                        Nature::Primitive(Primitive::String(OriginType::from(ident.clone())))
                    }
                    "number" => {
                        Nature::Primitive(Primitive::Number(OriginType::from(ident.clone())))
                    }
                    _ => Nature::Referred(Referred::Ref(serialized, Some(context.clone()))),
                }
            }
        })
    }
}

impl Extract<&Punctuated<PathSegment, PathSep>> for Nature {
    fn extract(
        segments: &Punctuated<PathSegment, PathSep>,
        context: Context,
        cfg: &Config,
    ) -> Result<Nature, E> {
        if let Some(segment) = segments.last() {
            let mut ty = match segment.ident.to_string().as_str() {
                "Vec" => Nature::Composite(Composite::Vec(OriginType::from(segment.clone()), None)),
                "HashMap" => Nature::Composite(Composite::HashMap(
                    OriginType::from(segment.clone()),
                    None,
                    None,
                )),
                "Option" => {
                    Nature::Composite(Composite::Option(OriginType::from(segment.clone()), None))
                }
                "Result" => Nature::Composite(Composite::Result(
                    OriginType::from(segment.clone()),
                    None,
                    None,
                    context.exception_suppression()?,
                    false,
                )),
                _ => Nature::extract(&segment.ident, context.clone(), cfg)?,
            };
            if let PathArguments::AngleBracketed(args) = &segment.arguments {
                for arg in args.args.iter() {
                    ty.bind(Nature::extract(arg, context.clone(), cfg)?)?;
                }
            }
            Ok(ty)
        } else {
            Err(E::Parsing(String::from(
                "For not primitive types expected at least one segment",
            )))
        }
    }
}

impl Extract<&TypeTuple> for Nature {
    fn extract(ty: &TypeTuple, context: Context, cfg: &Config) -> Result<Nature, E> {
        if ty.elems.is_empty() {
            Ok(Nature::Composite(Composite::Undefined(OriginType::from(
                ty.clone(),
            ))))
        } else {
            let mut nature =
                Nature::Composite(Composite::Tuple(OriginType::from(ty.clone()), vec![]));
            for element in ty.elems.iter() {
                nature.bind(Nature::extract(element, context.clone(), cfg)?)?;
            }
            Ok(nature)
        }
    }
}

impl Extract<&Type> for Nature {
    fn extract(ty: &Type, context: Context, cfg: &Config) -> Result<Nature, E> {
        match ty {
            Type::Array(ty_array) => {
                let inner = Nature::extract(ty_array.elem.as_ref(), context, cfg)?;
                if !matches!(inner, Nature::Primitive(..)) {
                    return Err(E::NotSupported(format!(
                        "{} isn't supported in Array",
                        inner.type_as_string()?
                    )));
                }
                Ok(Nature::Composite(Composite::Array(Box::new(inner))))
            }
            Type::Reference(ty_ref) => Nature::extract(ty_ref.elem.as_ref(), context, cfg),
            Type::Path(type_path) => {
                if let Some(ident) = type_path.path.get_ident() {
                    Nature::extract(ident, context, cfg)
                } else {
                    Nature::extract(&type_path.path.segments, context, cfg)
                }
            }
            Type::Tuple(type_tuple) => Nature::extract(type_tuple, context, cfg),
            _ => Err(E::NotSupported("Type".to_owned())),
        }
    }
}

impl Extract<Type> for Nature {
    fn extract(ty: Type, context: Context, cfg: &Config) -> Result<Nature, E> {
        Self::extract(&ty, context, cfg)
    }
}

impl Extract<&ImplItemFn> for Nature {
    fn extract(fn_item: &ImplItemFn, context: Context, cfg: &Config) -> Result<Nature, E> {
        let mut args = vec![];
        for fn_arg in fn_item.sig.inputs.iter() {
            if let FnArg::Typed(ty) = fn_arg {
                let arg_name = if let Pat::Ident(id) = *ty.pat.clone() {
                    id.ident.to_string()
                } else {
                    return Err(E::Parsing(String::from("Cannot find ident for FnArg")));
                };
                args.push(Nature::Referred(Referred::FuncArg(
                    serialize_name(&arg_name),
                    context.clone(),
                    Box::new(Nature::extract(*ty.ty.clone(), context.clone(), cfg)?),
                    context.get_bound(&arg_name),
                )));
            }
        }
        let out = get_fn_return(
            &fn_item.sig.output,
            &context,
            fn_item.sig.asyncness.is_some(),
            cfg,
        )?;
        let constructor = if let Some(Nature::Referred(Referred::Ref(re, _))) = out.as_deref() {
            re == "Self"
        } else {
            false
        } || context.as_constructor();
        Ok(Self::Composite(Composite::Func(
            OriginType::from(fn_item.clone()),
            args,
            out,
            fn_item.sig.asyncness.is_some(),
            constructor,
        )))
    }
}

impl Extract<&ItemFn> for Nature {
    fn extract(fn_item: &ItemFn, context: Context, cfg: &Config) -> Result<Nature, E> {
        let mut args = vec![];
        for fn_arg in fn_item.sig.inputs.iter() {
            if let FnArg::Typed(ty) = fn_arg {
                let arg_name = if let Pat::Ident(id) = *ty.pat.clone() {
                    id.ident.to_string()
                } else {
                    return Err(E::Parsing(String::from("Cannot find ident for FnArg")));
                };
                args.push(Nature::Referred(Referred::FuncArg(
                    serialize_name(&arg_name),
                    context.clone(),
                    Box::new(Nature::extract(*ty.ty.clone(), context.clone(), cfg)?),
                    context.get_bound(&arg_name),
                )));
            }
        }
        let out = get_fn_return(
            &fn_item.sig.output,
            &context,
            fn_item.sig.asyncness.is_some(),
            cfg,
        )?;
        let constructor = if let Some(Nature::Referred(Referred::Ref(re, _))) = out.as_deref() {
            re == "Self"
        } else {
            false
        } || context.as_constructor();
        Ok(Self::Composite(Composite::Func(
            OriginType::from(fn_item.clone()),
            args,
            out,
            fn_item.sig.asyncness.is_some(),
            constructor,
        )))
    }
}