swc_magic/
lib.rs

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
39/// Handles functions from `@swc/magic`.
40struct 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        // Remove Stmt::Empty
96        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}