1#![feature(box_patterns)]
2
3use import_analyzer::ImportMap;
4use serde::Deserialize;
5use swc_atoms::Atom;
6use swc_common::{
7 comments::Comments, errors::HANDLER, util::take::Take, Mark, Span, Spanned, DUMMY_SP,
8};
9use swc_ecma_ast::{CallExpr, Callee, EmptyStmt, Expr, Module, ModuleDecl, ModuleItem, Stmt};
10use swc_ecma_visit::{VisitMut, VisitMutWith};
11
12mod import_analyzer;
13
14#[derive(Debug, Clone, Deserialize)]
15pub struct Config {
16 #[serde(default = "default_import_path")]
17 pub import_path: Atom,
18}
19
20fn default_import_path() -> Atom {
21 Atom::from("@swc/magic")
22}
23
24impl Config {}
25
26pub fn swc_magic<C>(_unreolved_mark: Mark, config: Config, comments: C) -> impl VisitMut
27where
28 C: Comments,
29{
30 Magic {
31 config,
32 comments,
33 imports: Default::default(),
34 }
35}
36
37const MARK_AS_PURE_FN_NAME: &str = "markAsPure";
38
39struct Magic<C>
41where
42 C: Comments,
43{
44 config: Config,
45 comments: C,
46 imports: ImportMap,
47}
48
49impl<C> VisitMut for Magic<C>
50where
51 C: Comments,
52{
53 fn visit_mut_expr(&mut self, e: &mut Expr) {
54 e.visit_mut_children_with(self);
55
56 if let Expr::Call(CallExpr {
57 span,
58 callee: Callee::Expr(callee),
59 args,
60 ..
61 }) = e
62 {
63 if !self
64 .imports
65 .is_import(callee, &self.config.import_path, MARK_AS_PURE_FN_NAME)
66 {
67 return;
68 }
69
70 if args.len() != 1 {
71 HANDLER.with(|handler| {
72 handler
73 .struct_span_err(*span, "markAsPure() does not support multiple arguments")
74 .emit();
75 });
76 return;
77 }
78
79 *e = *args[0].expr.take();
80
81 let mut lo = e.span().lo;
82 if lo.is_dummy() {
83 lo = Span::dummy_with_cmt().lo;
84 }
85
86 self.comments.add_pure_comment(lo);
87 }
88 }
89
90 fn visit_mut_module(&mut self, m: &mut Module) {
91 self.imports = ImportMap::analyze(m);
92
93 m.visit_mut_children_with(self);
94
95 m.body
97 .retain(|item| !matches!(item, ModuleItem::Stmt(Stmt::Empty(..))));
98 }
99
100 fn visit_mut_module_item(&mut self, m: &mut ModuleItem) {
101 if let ModuleItem::ModuleDecl(ModuleDecl::Import(import)) = m {
102 if import.src.value == self.config.import_path {
103 *m = ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
104 return;
105 }
106 }
107
108 m.visit_mut_children_with(self);
109 }
110}