resw 0.5.1

A library for writing RESSA AST parts to a file
Documentation
use resast::prelude::*;
use ressa::Parser;
use resw::Writer;
use std::{
    fs::{read_to_string, File},
    io::BufWriter,
};

fn main() {
    pretty_env_logger::init();
    let mut args = ::std::env::args();
    let _ = args.next();
    let file_name = args
        .next()
        .unwrap_or(String::from("./examples/insert_logging.js"));
    let js = read_to_string(file_name).expect("Unable to find js file");
    let p = Parser::new(&js).expect("Failed to create parser");
    let f = File::create("./examples/inserted.js").expect("failed to create file");
    let mut w = Writer::new(BufWriter::new(f));
    for ref part in p.map(|r| r.unwrap()).map(|p| map_part(vec![], p)) {
        w.write_part(part).expect("failed to write part");
    }
}

fn map_part<'a>(args: Vec<Expr<'a>>, part: ProgramPart<'a>) -> ProgramPart<'a> {
    match part {
        ProgramPart::Decl(decl) => ProgramPart::Decl(map_decl(args, decl)),
        ProgramPart::Stmt(stmt) => ProgramPart::Stmt(map_stmt(args, stmt)),
        ProgramPart::Dir(_) => part,
    }
}

fn map_decl<'a>(mut args: Vec<Expr<'a>>, decl: Decl<'a>) -> Decl<'a> {
    match decl {
        Decl::Func(f) => Decl::Func(map_func(args, f)),
        Decl::Class(class) => Decl::Class(map_class(args, class)),
        Decl::Var(kind, del) => Decl::Var(
            kind,
            del.into_iter()
                .map(|part| {
                    if let Pat::Ident(ref ident) = part.id {
                        args.push(ident_to_string_lit(ident));
                    }
                    VarDecl {
                        id: part.id,
                        init: part.init.map(|e| map_expr(args.clone(), e)),
                    }
                })
                .collect(),
        ),
        _ => decl.clone(),
    }
}

fn map_stmt<'a>(args: Vec<Expr<'a>>, stmt: Stmt<'a>) -> Stmt<'a> {
    match stmt {
        Stmt::Expr(expr) => Stmt::Expr(map_expr(args, expr)),
        _ => stmt.clone(),
    }
}

fn map_expr<'a>(mut args: Vec<Expr<'a>>, expr: Expr<'a>) -> Expr<'a> {
    match expr {
        Expr::Func(f) => Expr::Func(map_func(args, f)),
        Expr::Class(c) => Expr::Class(map_class(args, c)),
        Expr::ArrowFunc(f) => Expr::ArrowFunc(map_arrow_func(args, f)),
        Expr::Assign(mut assign) => {
            if let Some(expr) = assign_left_to_string_lit(&assign.left) {
                args.push(expr);
            }
            assign.right = Box::new(map_expr(args, *assign.right));
            Expr::Assign(assign.clone())
        }
        _ => expr.clone(),
    }
}

fn map_func<'a>(mut args: Vec<Expr<'a>>, mut func: Func<'a>) -> Func<'a> {
    if let Some(ref id) = &func.id {
        args.push(ident_to_string_lit(id));
    }
    let local_args = extract_idents_from_args(&func.params);
    func.body = FuncBody(
        func.body
            .0
            .into_iter()
            .map(|p| map_part(args.clone(), p))
            .collect(),
    );
    insert_expr_into_func(
        console_log(
            args.clone()
                .into_iter()
                .chain(local_args.into_iter())
                .collect(),
        ),
        &mut func,
    );
    func
}

fn map_class<'a>(mut args: Vec<Expr<'a>>, mut class: Class<'a>) -> Class<'a> {
    if let Some(ref id) = class.id {
        args.push(ident_to_string_lit(id))
    }
    let mut new_body = vec![];
    for item in class.body.0 {
        new_body.push(map_class_prop(args.clone(), item))
    }
    class.body = ClassBody(new_body);
    class
}

fn map_class_prop<'a>(mut args: Vec<Expr<'a>>, mut prop: Prop<'a>) -> Prop<'a> {
    match prop.kind {
        PropKind::Ctor => {
            args.insert(
                args.len().saturating_sub(1),
                Expr::Lit(Lit::String(StringLit::single_from("new"))),
            );
        }
        PropKind::Get => {
            args.push(Expr::Lit(Lit::String(StringLit::single_from("get"))));
        }
        PropKind::Set => {
            args.push(Expr::Lit(Lit::String(StringLit::single_from("set"))));
        }
        _ => (),
    };
    match &prop.key {
        PropKey::Expr(ref expr) => match expr {
            Expr::Ident(ref i) => {
                if i.name != "constructor" {
                    args.push(ident_to_string_lit(i));
                }
            }
            _ => (),
        },
        PropKey::Lit(ref l) => match l {
            Lit::Boolean(_) | Lit::Number(_) | Lit::RegEx(_) | Lit::String(_) => {
                args.push(Expr::Lit(l.clone()))
            }
            Lit::Null => {
                args.push(Expr::Lit(Lit::String(StringLit::Single(
                    ::std::borrow::Cow::Owned(String::from("null")),
                ))));
            }
            _ => (),
        },
        PropKey::Pat(ref p) => match p {
            Pat::Ident(ref i) => args.push(ident_to_string_lit(i)),
            _ => args.extend(extract_idents_from_pat(p).into_iter().filter_map(|e| e)),
        },
    }
    if let PropValue::Expr(expr) = prop.value {
        prop.value = PropValue::Expr(map_expr(args, expr));
    }
    prop
}

fn map_arrow_func<'a>(mut args: Vec<Expr<'a>>, mut f: ArrowFuncExpr<'a>) -> ArrowFuncExpr<'a> {
    args.extend(extract_idents_from_args(&f.params));
    match &mut f.body {
        ArrowFuncBody::FuncBody(ref mut body) => {
            insert_expr_into_func_body(console_log(args), body)
        }
        ArrowFuncBody::Expr(expr) => {
            f.body = ArrowFuncBody::FuncBody(FuncBody(vec![
                console_log(args),
                ProgramPart::Stmt(Stmt::Return(Some(*expr.clone()))),
            ]))
        }
    }
    f
}

fn assign_left_to_string_lit<'a>(left: &AssignLeft<'a>) -> Option<Expr<'a>> {
    match left {
        AssignLeft::Expr(expr) => expr_to_string_lit(expr),
        AssignLeft::Pat(pat) => match pat {
            Pat::Ident(ident) => Some(ident_to_string_lit(ident)),
            _ => None,
        },
    }
}

fn extract_idents_from_args<'a>(args: &[FuncArg<'a>]) -> Vec<Expr<'a>> {
    let mut ret = vec![];
    for arg in args {
        match arg {
            FuncArg::Expr(expr) => ret.push(extract_ident_from_expr(expr)),
            FuncArg::Pat(pat) => ret.extend(extract_idents_from_pat(pat)),
        }
    }
    ret.into_iter().filter_map(|e| e).collect()
}

fn extract_ident_from_expr<'a>(expr: &Expr<'a>) -> Option<Expr<'a>> {
    match expr {
        Expr::Ident(ident) => Some(Expr::Ident(ident.clone())),
        _ => None,
    }
}

fn extract_idents_from_pat<'a>(pat: &Pat<'a>) -> Vec<Option<Expr<'a>>> {
    match pat {
        Pat::Ident(i) => {
            vec![Some(Expr::Ident(i.clone()))]
        }
        Pat::Obj(obj) => obj
            .iter()
            .map(|part| match part {
                ObjPatPart::Rest(pat) => extract_idents_from_pat(pat),
                ObjPatPart::Assign(prop) => match prop.key {
                    PropKey::Pat(ref pat) => extract_idents_from_pat(pat),
                    PropKey::Expr(ref expr) => {
                        vec![extract_ident_from_expr(expr)]
                    }
                    PropKey::Lit(ref lit) => {
                        vec![Some(Expr::Lit(lit.clone()))]
                    }
                },
            })
            .flatten()
            .collect(),
        Pat::Array(arr) => arr
            .iter()
            .map(|p| match p {
                Some(ArrayPatPart::Expr(expr)) => {
                    vec![extract_ident_from_expr(expr)]
                }
                Some(ArrayPatPart::Pat(pat)) => extract_idents_from_pat(pat),
                None => vec![],
            })
            .flatten()
            .collect(),
        Pat::RestElement(pat) => extract_idents_from_pat(pat),
        Pat::Assign(assign) => extract_idents_from_pat(&*assign.left),
    }
}

fn expr_to_string_lit<'a>(e: &Expr<'a>) -> Option<Expr<'a>> {
    let inner = expr_to_string(e)?;
    Some(Expr::Lit(Lit::String(StringLit::Single(
        ::std::borrow::Cow::Owned(inner),
    ))))
}

fn expr_to_string(expr: &Expr) -> Option<String> {
    match expr {
        Expr::Ident(ref ident) => Some(ident.name.to_string()),
        Expr::This => Some("this".to_string()),
        Expr::Member(ref mem) => {
            let prefix = if let Some(s) = expr_to_string(&mem.object) {
                s
            } else {
                return None;
            };
            let suffix = if let Some(s) = expr_to_string(&mem.property) {
                s
            } else {
                return None;
            };
            Some(format!("{}.{}", prefix, suffix))
        }
        Expr::Lit(lit) => match lit {
            Lit::String(s) => Some(s.clone_inner().to_string()),
            Lit::Number(n) => Some(n.to_string()),
            Lit::RegEx(r) => Some(format!("/{}/{}", r.pattern, r.flags)),
            Lit::Boolean(b) => Some(b.to_string()),
            Lit::Null => Some("null".to_string()),
            _ => None,
        },
        _ => None,
    }
}

fn ident_to_string_lit<'a>(i: &Ident<'a>) -> Expr<'a> {
    Expr::Lit(Lit::String(StringLit::Single(i.name.clone())))
}

fn insert_expr_into_func<'a>(expr: ProgramPart<'a>, func: &mut Func<'a>) {
    insert_expr_into_func_body(expr, &mut func.body);
}

fn insert_expr_into_func_body<'a>(expr: ProgramPart<'a>, body: &mut FuncBody<'a>) {
    body.0.insert(0, expr);
}

pub fn console_log<'a>(args: Vec<Expr<'a>>) -> ProgramPart<'a> {
    ProgramPart::Stmt(Stmt::Expr(Expr::Call(CallExpr {
        callee: Box::new(Expr::Member(MemberExpr {
            computed: false,
            object: Box::new(Expr::ident_from("console")),
            property: Box::new(Expr::ident_from("log")),
        })),
        arguments: args,
    })))
}