actypes 1.2.0

Add runtime type checking to your Typescript code
Documentation
use std::{fs, path::PathBuf, str::FromStr};

use crate::{
    act_structs::{
        get_js_constructor_from_acttype, get_ts_type_from_acttype,
        get_typeinfo_operator_from_acttype, ParamAct, PatchAct, TypeAct,
    },
    args_parser::ActArgs,
    patch_index_helper::PatchIndexHelper,
};
use clap::{Parser, ValueEnum};

#[derive(Debug, Clone, ValueEnum)]
pub enum PatchType {
    Warning,
    Error,
    Fix,
}
impl FromStr for PatchType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "warning" => Ok(PatchType::Warning),
            "error" => Ok(PatchType::Error),
            "fix" => Ok(PatchType::Fix),
            _ => Err(String::from("Invalid enum value")),
        }
    }
}

pub fn gen_param_type_check_patch(
    param: ParamAct,
    symbol_name: &String,
    file_name: &String,
) -> String {
    let args = ActArgs::parse();
    let patch_type = args.patch_type;
    let param_ts_type = get_ts_type_from_acttype(&param.act_type);
    let param_js_constructor = get_js_constructor_from_acttype(&param.act_type);
    let log_message = format!(
        r#"`[{}=>{}] {} isn't of type {} but of type ${{typeof {}}}`"#,
        file_name, symbol_name, param.name, param_ts_type, param.name
    );
    let patch_body = match patch_type {
        PatchType::Fix => format!(
            r#"console.warn({}," and was casted"); {}({});"#,
            log_message, param_js_constructor, param.name
        ),
        PatchType::Error => format!(r#"throw new TypeError({});"#, log_message),
        PatchType::Warning => format!(r#"console.warn({});"#, log_message),
    };
    let typeinfo_operator = get_typeinfo_operator_from_acttype(&param.act_type);

    let conditional_string = match typeinfo_operator.as_str() {
        "instanceof" => format!(
            r#"!({} {} {})"#,
            param.name, typeinfo_operator, param_ts_type
        ),
        "typeof" => format!(
            r#"{} {} !== '{}'"#,
            typeinfo_operator, param.name, param_ts_type
        ),
        _ => "true".to_string(),
    };
    let patch_string = format!(
        r#"
    if({}){{
    {}
    }}
    "#,
        conditional_string, patch_body
    );
    patch_string
}

pub fn get_function_param_patch(
    param: ParamAct,
    body_start: u32,
    symbol_name: &String,
    file_name: &String,
) -> PatchAct {
    let patch_string = gen_param_type_check_patch(param, symbol_name, file_name);
    return PatchAct {
        byte_pos: body_start,
        patch: patch_string.as_bytes().to_vec(),
    };
}

pub fn get_function_params_patches(
    params: Vec<ParamAct>,
    body_start: u32,
    symbol_name: String,
    file_name: String,
) -> Vec<PatchAct> {
    let mut params_patches: Vec<PatchAct> = vec![];
    for param in params
        .into_iter()
        .filter(|x| x.act_type != TypeAct::Unknown)
    {
        params_patches.push(get_function_param_patch(
            param,
            body_start,
            &symbol_name,
            &file_name,
        ));
    }
    params_patches
}

pub fn apply_patches(patches: Vec<PatchAct>, file_path: PathBuf) -> Result<(), String> {
    let mut buffer = fs::read(&file_path).unwrap_or_default();
    let mut patch_index_helper = PatchIndexHelper::new();
    for patch in patches {
        let pos: usize = patch_index_helper.get_drifted_index(patch.byte_pos) as usize;
        let patch_len: u32 = patch.patch.len() as u32;
        buffer.splice(pos..pos, patch.patch);
        patch_index_helper.register_patched_index(patch.byte_pos, patch_len)
    }
    let args = ActArgs::parse();
    let out_folder_path = args.out_folder_path;
    let in_folder_path = args.folder_path;

    let mut patched_file_path = file_path.clone();
    // remove the first
    patched_file_path = patched_file_path
        .strip_prefix(&in_folder_path)
        .unwrap()
        .to_path_buf();
    patched_file_path = PathBuf::from(out_folder_path).join(patched_file_path);
    let patch_file_path_without_filename = patched_file_path
        .parent()
        .unwrap_or_else(|| {
            println!("Fail to get parent of {:?}", patched_file_path);
            panic!("Fail to get parent of {:?}", patched_file_path);
        })
        .to_path_buf();
    fs::create_dir_all(patch_file_path_without_filename).unwrap_or_else(|err| {
        println!("{:?}", err);
        panic!("Fail to create out_folder_path");
    });
    fs::write(patched_file_path, buffer).unwrap();
    Ok(())
}