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
use serde::Deserialize;
use swc_core::{
    common::util::take::Take,
    ecma::{
        ast::{CallExpr, Ident, MemberExpr, Program, Stmt},
        visit::{as_folder, FoldWith, VisitMut, VisitMutWith},
    },
    plugin::{plugin_transform, proxies::TransformPluginProgramMetadata},
};

#[cfg(test)]
mod tests;

#[derive(Deserialize, Default)]
#[serde(default)]
pub struct Options {
    exclude: Vec<String>,
}

const SPECIFY_SUBCOMMAND: [&str; 4] = ["log", "warn", "error", "info"];

pub struct RemoveConsole {
    options: Options,
}

impl RemoveConsole {
    fn eq(&self, ident: Option<&Ident>, eq_name: &str) -> bool {
        if let Some(ident) = ident {
            return ident.sym.eq(eq_name);
        }
        false
    }

    fn is_console(&self, expr: &MemberExpr) -> bool {
        let obj = &expr.obj;
        self.eq(obj.as_ident(), "console")
    }

    fn is_specify_subcommand(&self, expr: &MemberExpr) -> bool {
        let prop = &expr.prop;
        for command in SPECIFY_SUBCOMMAND {
            if self.options.exclude.contains(&command.to_string()) {
                continue;
            }
            if self.eq(prop.as_ident(), command) {
                return true;
            }
        }
        return false;
    }

    fn should_remove(&self, e: &CallExpr) -> bool {
        if e.callee.is_expr() {
            if let Some(expr) = e.callee.as_expr() {
                if let Some(expr) = expr.as_member() {
                    return self.is_console(expr) && self.is_specify_subcommand(expr);
                }
            }
        }
        false
    }
}

impl VisitMut for RemoveConsole {
    fn visit_mut_stmt(&mut self, stmt: &mut Stmt) {
        stmt.visit_mut_children_with(self);

        if let Stmt::Expr(expr) = stmt {
            if let Some(call_expr) = expr.expr.as_call() {
                if self.should_remove(call_expr) {
                    stmt.take();
                }
            }
        }
    }
}

#[plugin_transform]
pub fn remove_console(program: Program, metadata: TransformPluginProgramMetadata) -> Program {
    let options = metadata
        .get_transform_plugin_config()
        .map(|json| {
            serde_json::from_str::<Options>(&json)
                .expect("failed to deserialize options for remove-console plugin")
        })
        .unwrap_or_default();
    program.fold_with(&mut as_folder(RemoveConsole { options }))
}