swc_plugin_remove_console/
lib.rs1use serde::Deserialize;
2use swc_core::{
3 common::util::take::Take,
4 ecma::{
5 ast::{CallExpr, MemberExpr, Program, Stmt},
6 visit::{visit_mut_pass, VisitMut, VisitMutWith},
7 },
8 plugin::{plugin_transform, proxies::TransformPluginProgramMetadata},
9};
10
11#[cfg(test)]
12mod tests;
13
14#[derive(Deserialize, Default)]
15#[serde(default)]
16pub struct Options {
17 exclude: Vec<String>,
18}
19
20const SPECIFY_SUBCOMMAND: [&str; 4] = ["log", "warn", "error", "info"];
21
22pub struct RemoveConsole {
23 options: Options,
24}
25
26impl RemoveConsole {
27 fn is_console(&self, expr: &MemberExpr) -> bool {
28 let obj = &expr.obj;
29 obj.as_ident().map_or(false, |ident| ident.sym == "console")
30 }
31
32 fn is_specify_subcommand(&self, expr: &MemberExpr) -> bool {
33 let prop = &expr.prop;
34 return SPECIFY_SUBCOMMAND.iter().any(|command| {
35 if self.options.exclude.contains(&command.to_string()) {
36 return false;
37 }
38 prop.as_ident().map_or(false, |ident| ident.sym == *command)
39 });
40 }
41
42 fn should_remove(&self, e: &CallExpr) -> bool {
43 if e.callee.is_expr() {
44 if let Some(expr) = e.callee.as_expr() {
45 if let Some(expr) = expr.as_member() {
46 return self.is_console(expr) && self.is_specify_subcommand(expr);
47 }
48 }
49 }
50 false
51 }
52}
53
54impl VisitMut for RemoveConsole {
55 fn visit_mut_stmt(&mut self, stmt: &mut Stmt) {
56 stmt.visit_mut_children_with(self);
57
58 if let Stmt::Expr(expr) = stmt {
59 if let Some(call_expr) = expr.expr.as_call() {
60 if self.should_remove(call_expr) {
61 stmt.take();
62 }
63 }
64 }
65 }
66}
67
68#[plugin_transform]
69pub fn remove_console(program: Program, metadata: TransformPluginProgramMetadata) -> Program {
70 let options = metadata
71 .get_transform_plugin_config()
72 .map(|json| {
73 serde_json::from_str::<Options>(&json)
74 .expect("failed to deserialize options for remove-console plugin")
75 })
76 .unwrap_or_default();
77 program.apply(&mut visit_mut_pass(RemoveConsole { options }))
78}