swc_ecma_compat_es2015/
block_scoped_fn.rs

1use swc_common::{util::take::Take, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_utils::IdentUsageFinder;
4use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
5use swc_trace_macro::swc_trace;
6
7pub fn block_scoped_functions() -> impl Pass {
8    visit_mut_pass(BlockScopedFns)
9}
10
11#[derive(Clone, Copy)]
12struct BlockScopedFns;
13
14#[swc_trace]
15impl VisitMut for BlockScopedFns {
16    noop_visit_mut_type!(fail);
17
18    fn visit_mut_function(&mut self, n: &mut Function) {
19        let Some(body) = &mut n.body else { return };
20
21        n.params.visit_mut_with(self);
22
23        // skip function scope
24        body.visit_mut_children_with(self);
25    }
26
27    fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
28        n.visit_mut_children_with(self);
29
30        let mut stmts = Vec::with_capacity(n.stmts.len());
31        let mut extra_stmts = Vec::with_capacity(n.stmts.len());
32
33        for stmt in n.stmts.take() {
34            if let Stmt::Expr(ExprStmt { ref expr, .. }) = stmt {
35                if let Expr::Lit(Lit::Str(..)) = &**expr {
36                    stmts.push(stmt);
37                    continue;
38                }
39            }
40
41            if let Stmt::Decl(Decl::Fn(decl)) = stmt {
42                if IdentUsageFinder::find(&decl.ident.to_id(), &decl.function) {
43                    extra_stmts.push(decl.into());
44                    continue;
45                }
46                stmts.push(
47                    VarDecl {
48                        span: DUMMY_SP,
49                        kind: VarDeclKind::Let,
50                        decls: vec![VarDeclarator {
51                            span: DUMMY_SP,
52                            name: decl.ident.clone().into(),
53                            init: Some(Box::new(Expr::Fn(FnExpr {
54                                ident: Some(decl.ident),
55                                function: decl.function,
56                            }))),
57                            definite: false,
58                        }],
59                        ..Default::default()
60                    }
61                    .into(),
62                )
63            } else {
64                extra_stmts.push(stmt)
65            }
66        }
67
68        stmts.append(&mut extra_stmts);
69
70        n.stmts = stmts
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use swc_ecma_transforms_testing::test;
77
78    use super::*;
79
80    test!(
81        ::swc_ecma_parser::Syntax::default(),
82        |_| block_scoped_functions(),
83        hoisting,
84        r#"
85{
86    function fn1() { fn2(); }
87
88    fn1();
89
90    function fn2() { }
91}
92"#
93    );
94
95    test!(
96        ::swc_ecma_parser::Syntax::default(),
97        |_| block_scoped_functions(),
98        basic,
99        r#"{
100  function name (n) {
101    return n;
102  }
103}
104
105name("Steve");"#
106    );
107
108    test!(
109        ::swc_ecma_parser::Syntax::default(),
110        |_| block_scoped_functions(),
111        basic_2,
112        r#"
113        {
114            function foo() {
115                return function bar() {
116                    {
117                        function baz() {}
118                    }
119                };
120                function baz() {}
121                {
122                    function bar() {}
123                    {
124                        function bar() {}
125                    }
126                }
127            }
128        }
129        "#
130    );
131
132    test!(
133        ::swc_ecma_parser::Syntax::default(),
134        |_| block_scoped_functions(),
135        issue_271,
136        "
137function foo(scope) {
138    scope.startOperation = startOperation;
139
140    function startOperation(operation) {
141        scope.agentOperation = operation;
142    }
143}
144"
145    );
146
147    test!(
148        ::swc_ecma_parser::Syntax::default(),
149        |_| block_scoped_functions(),
150        issue_288_1,
151        "function components_Link_extends() { components_Link_extends = Object.assign || function \
152         (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for \
153         (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { \
154         target[key] = source[key]; } } } return target; }; return \
155         components_Link_extends.apply(this, arguments); }
156
157"
158    );
159
160    test!(
161        ::swc_ecma_parser::Syntax::default(),
162        |_| block_scoped_functions(),
163        issue_288_2,
164        "function _extends() {
165  module.exports = _extends = Object.assign || function (target) {
166    for (var i = 1; i < arguments.length; i++) {
167      var source = arguments[i];
168
169      for (var key in source) {
170        if (Object.prototype.hasOwnProperty.call(source, key)) {
171          target[key] = source[key];
172        }
173      }
174    }
175
176    return target;
177  };
178
179  return _extends.apply(this, arguments);
180}
181"
182    );
183
184    test!(
185        ::swc_ecma_parser::Syntax::default(),
186        |_| block_scoped_functions(),
187        hoisting_directives,
188        "function foo() {
189            'use strict';
190            function _interop_require_default(obj) {
191              return obj && obj.__esModule ? obj : {
192                default: obj
193              };
194            }
195        }"
196    );
197}