reluxscript 0.1.4

Write AST transformations once. Compile to Babel, SWC, and beyond.
Documentation
// Generated by ReluxScript compiler
// Do not edit manually

use swc_common::{Span, DUMMY_SP, SyntaxContext};
use swc_ecma_ast::*;
use swc_ecma_visit::{Visit, VisitMut, VisitMutWith, VisitWith};
use std::collections::HashSet;

#[derive(Clone, Debug)]
struct HookInfo {
    name: String,
    hook_type: String,
    args_count: i32,
}

#[derive(Clone, Debug)]
struct ComponentStats {
    name: String,
    hooks: Vec<HookInfo>,
    has_jsx: bool,
}

#[derive(Clone, Debug)]
struct MemberInfo {
    object: String,
    property: String,
}

#[derive(Clone, Debug)]
struct State {
    components: Vec<ComponentStats>,
    current_component: Option<String>,
    removed_count: i32,
    visited_nodes: HashSet<String>,
}

pub struct KitchenSinkPlugin {
    pub state: State,
}

impl VisitMut for KitchenSinkPlugin {
    fn visit_mut_fn_decl(&mut self, node: &mut FnDecl) {
        let name = node.ident.sym.to_string();
        if Self::is_component_name(&name) {
            self.state.current_component = Some(name.clone());
            let stats = ComponentStats { name: name.clone(), hooks: vec![], has_jsx: false };
            self.state.components.push(stats)
        }
        node.visit_mut_children_with(self);
        self.state.current_component = None;
    }
    
    fn visit_mut_call_expr(&mut self, node: &mut CallExpr) {
        if let Some(component_name) = &self.state.current_component {
            if let Some(callee_name) = Self::get_callee_name(&node.callee.as_expr().unwrap()) {
                if Self::is_hook_call(&callee_name) {
                    let hook_info = HookInfo { name: callee_name.clone(), hook_type: Self::categorize_hook(&callee_name), args_count: 0 };
                    for component in &mut self.state.components {
                        if (component.name == *component_name) {
                            component.hooks.push(hook_info);
                            break;
                        }
                    }
                }
            }
        }
        if let Some(member) = Self::extract_member_call(node) {
            if ((member.object == "console") && Self::should_remove_console(&member.property)) {
                self.state.removed_count += 1
            }
        }
        node.visit_mut_children_with(self);
    }
    
    fn visit_mut_ident(&mut self, node: &mut Ident) {
        let name = node.sym.to_string();
        self.state.visited_nodes.insert(name.clone());
        if (name == "oldName") {
            *node = Ident { sym: "newName".into(), span: DUMMY_SP, optional: false, ctxt: SyntaxContext::empty() }.into()
        }
        if match node.sym.to_string().as_str() {
            "foo" | "bar" | "baz" => {
                true
            }
            _ => {
                false
            }
        } {
            let new_name = format!("renamed_{}", node.sym.to_string());
            *node = Ident { sym: new_name.into(), span: DUMMY_SP, optional: false, ctxt: SyntaxContext::empty() }.into()
        }
    }
    
    fn visit_mut_jsx_element(&mut self, node: &mut JSXElement) {
        if let Some(component_name) = &self.state.current_component {
            for component in &mut self.state.components {
                if (component.name == *component_name) {
                    let updated = ComponentStats { name: component.name.to_string(), hooks: component.hooks.clone(), has_jsx: true };
                    *component = updated;
                    break;
                }
            }
        }
        node.visit_mut_children_with(self);
    }
    
    fn visit_mut_var_declarator(&mut self, node: &mut VarDeclarator) {
        if let Some(init) = &node.init {
            if let Expr::Array(arr) = init.as_ref() {
                let _size = arr.elems.len();
            }
        }
        node.visit_mut_children_with(self);
    }
    
}

impl KitchenSinkPlugin {
    pub fn new() -> Self {
        Self {
            state: State {
                components: Vec::new(),
                current_component: None,
                removed_count: 0,
                visited_nodes: HashSet::new(),
            },
        }
    }
    
    fn is_component_name(name: &String) -> bool {
        if name.is_empty() {
            return false;
        }
        let first_char = name.chars().next().unwrap();
        first_char.is_uppercase()
    }
    
    fn is_hook_call(name: &String) -> bool {
        (name.starts_with("use") && (name.len() > 3))
    }
    
    fn categorize_hook(name: &String) -> String {
        if ((name == "useState") || (name == "useReducer")) {
            return "state".into();
        }
        if ((name == "useEffect") || (name == "useLayoutEffect")) {
            return "effect".into();
        }
        if (name == "useRef") {
            return "ref".into();
        }
        if ((name == "useMemo") || (name == "useCallback")) {
            return "memo".into();
        }
        return format!("custom:{}", name);
    }
    
    fn should_remove_console(method: &String) -> bool {
        (((method == "log") || (method == "warn")) || (method == "debug"))
    }
    
    fn format_stats(stats: &ComponentStats) -> String {
        format!("{} has {} hooks", stats.name, stats.hooks.len())
    }
    
    fn get_callee_name(callee: &Expr) -> Option<String> {
        if let Expr::Ident(id) = callee {
            Some(id.sym.to_string())
        } else {
            if let Expr::Member(member) = callee {
                Some(member.prop.clone())
            } else {
                None
            }
        }
    }
    
    fn extract_member_call(call: &CallExpr) -> Option<MemberInfo> {
        if let Callee::Expr(__callee_expr) = &&call.callee.as_expr().unwrap() {
            if let Expr::Member(member) = __callee_expr.as_ref() {
                if let Expr::Ident(obj) = &member.obj {
                    return Some(MemberInfo { object: obj.sym.to_string(), property: member.prop.clone() });
                }
            }
        }
        None
    }
    
    fn collect_hook_names(component: &ComponentStats) -> Vec<String> {
        component.hooks.iter().map(|h| h.name.clone()).collect()
    }
    
    fn has_hooks(component: &ComponentStats) -> bool {
        (component.hooks.len() > 0)
    }
    
    fn count_by_type(component: &ComponentStats, target: &String) -> i32 {
        let mut count = 0;
        for hook in &component.hooks {
            if (hook.hook_type == *target) {
                count += 1
            }
        }
        count
    }
    
    fn get_first_hook(stats: &ComponentStats) -> Option<String> {
        if stats.hooks.is_empty() {
            None
        } else {
            Some(stats.hooks[0].name.to_string())
        }
    }
    
    fn safe_get_name(stats: &ComponentStats) -> Result<String, String> {
        if stats.name.is_empty() {
            Err("No name".to_string())
        } else {
            Ok(stats.name.to_string())
        }
    }
    
    fn process_component(stats: &ComponentStats) -> Result<(), String> {
        let _name = Self::safe_get_name(stats)?;
        let _first_hook = Self::get_first_hook(stats).unwrap_or("none".into());
        Ok(())
    }
    
    fn get_hook_label(count: i32) -> String {
        if (count > 0) {
            "hooks".to_string()
        } else {
            "no hooks".to_string()
        }
    }
    
}