1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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 {};"#, 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(())
}