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}