swc_ecma_minifier/compress/optimize/
dead_code.rs

1use swc_common::util::take::Take;
2use swc_ecma_ast::*;
3
4use super::{BitCtx, Optimizer};
5use crate::program_data::{ScopeData, VarUsageInfoFlags};
6
7/// Methods related to option `dead_code`.
8impl Optimizer<'_> {
9    /// Optimize return value or argument of throw.
10    ///
11    /// This methods removes some useless assignments.
12    ///
13    /// # Example
14    ///
15    /// Note: `a` being declared in the function is important in the example
16    /// below.
17    ///
18    /// ```ts
19    /// function foo(){
20    ///     var a;
21    ///     throw a = x();
22    /// }
23    /// ```
24    ///
25    /// can be optimized as
26    ///
27    /// ```ts
28    /// function foo(){
29    ///     var a; // Will be dropped in next pass.
30    ///     throw x();
31    /// }
32    /// ```
33    /// # Returns
34    ///
35    /// returns true if `e` is changed.
36    pub(super) fn optimize_last_expr_before_termination(&mut self, e: &mut Expr) -> bool {
37        if !self.options.dead_code {
38            return false;
39        }
40
41        // A return statement in a try block may not terminate function.
42        if self.ctx.bit_ctx.contains(BitCtx::InTryBlock) {
43            return false;
44        }
45
46        if let Expr::Assign(assign @ AssignExpr { op: op!("="), .. }) = e {
47            self.optimize_last_expr_before_termination(&mut assign.right);
48
49            // We only handle identifiers on lhs for now.
50            if let Some(lhs) = assign.left.as_ident() {
51                let used_arguments = self
52                    .data
53                    .scopes
54                    .get(&self.ctx.scope)
55                    .map(|s| s.contains(ScopeData::USED_ARGUMENTS))
56                    .unwrap_or(false);
57
58                if self
59                    .data
60                    .vars
61                    .get(&lhs.to_id())
62                    .map(|var| {
63                        var.flags.contains(
64                            VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL),
65                        ) && !(used_arguments
66                            && var.flags.contains(VarUsageInfoFlags::DECLARED_AS_FN_PARAM))
67                            && !var.flags.intersects(VarUsageInfoFlags::EXPORTED)
68                    })
69                    .unwrap_or(false)
70                {
71                    report_change!(
72                        "dead_code: Dropping an assignment to a variable declared in function \
73                         because function is being terminated"
74                    );
75                    self.changed = true;
76                    *e = *assign.right.take();
77                    return true;
78                }
79            }
80        }
81
82        if let Expr::Assign(assign) = e {
83            // x += 1 => x + 1
84            if let Some(op) = assign.op.to_update() {
85                if op == op!("**") {
86                    return false;
87                }
88
89                if let Some(lhs) = assign.left.as_ident() {
90                    let used_arguments = self
91                        .data
92                        .scopes
93                        .get(&self.ctx.scope)
94                        .map(|s| s.contains(ScopeData::USED_ARGUMENTS))
95                        .unwrap_or(false);
96
97                    if self
98                        .data
99                        .vars
100                        .get(&lhs.to_id())
101                        .map(|var| {
102                            var.flags.contains(
103                                VarUsageInfoFlags::DECLARED.union(VarUsageInfoFlags::IS_FN_LOCAL),
104                            ) && !(used_arguments
105                                && var.flags.contains(VarUsageInfoFlags::DECLARED_AS_FN_PARAM))
106                                && !var.flags.contains(VarUsageInfoFlags::EXPORTED)
107                        })
108                        .unwrap_or(false)
109                    {
110                        report_change!(
111                            "dead_code: Converting an assignment into a binary expression in \
112                             function termination"
113                        );
114
115                        self.changed = true;
116                        *e = BinExpr {
117                            span: assign.span,
118                            op,
119                            left: lhs.clone().into(),
120                            right: assign.right.take(),
121                        }
122                        .into();
123                        return true;
124                    }
125                }
126            }
127        }
128
129        false
130    }
131}