use super::advanced_if::analyse_if_branch;
use super::advanced_if::process_if_branch;
use super::ctx::Ctx;
use super::ctx::MinifyScope;
use super::ctx::MinifySymbol;
use parse_js::ast::new_node;
use parse_js::ast::NodeData;
use parse_js::ast::Syntax;
use parse_js::operator::OperatorName;
use parse_js::visit::JourneyControls;
use parse_js::visit::Visitor;
use std::str::from_utf8_unchecked;
pub struct Pass1<'a, 'b> {
pub ctx: Ctx<'a, 'b>,
}
fn stmt_has_return<'a>(stx: &Syntax<'a>) -> bool {
match stx {
Syntax::ReturnStmt { .. } => true,
Syntax::BlockStmt { body } => body.iter().any(|n| stmt_has_return(&n.stx)),
_ => false,
}
}
impl<'a, 'b> Visitor<'a> for Pass1<'a, 'b> {
fn on_syntax_down(&mut self, n: &mut NodeData<'a>, _ctl: &mut JourneyControls) -> () {
let scope = n.scope;
match &mut n.stx {
Syntax::IfStmt {
consequent,
alternate,
..
} => {
match &consequent.stx {
Syntax::BlockStmt { .. } => {}
_ => {
let inner = consequent.take(self.ctx.session);
let wrapped = new_node(
self.ctx.session,
inner.scope,
inner.loc,
Syntax::BlockStmt {
body: {
let mut body = self.ctx.session.new_vec();
body.push(inner);
body
},
},
);
*consequent = wrapped;
}
};
if let Some(alt) = alternate {
match &alt.stx {
Syntax::BlockStmt { .. } => {}
_ => {
let inner = alt.take(self.ctx.session);
let wrapped = new_node(
self.ctx.session,
inner.scope,
inner.loc,
Syntax::BlockStmt {
body: {
let mut body = self.ctx.session.new_vec();
body.push(inner);
body
},
},
);
*alt = wrapped;
}
}
}
}
Syntax::BlockStmt { body } => {
let mut i = 0;
while i < body.len() {
if let Syntax::IfStmt {
test,
consequent,
alternate: maybe_alternate,
} = &mut body[i].stx
{
if let Some(alternate) = maybe_alternate {
let cons_has_return = stmt_has_return(&consequent.stx);
let swapped = if !cons_has_return && stmt_has_return(&alternate.stx) {
core::mem::swap(consequent, alternate);
let orig_test = test.take(self.ctx.session);
test.stx = Syntax::UnaryExpr {
parenthesised: false,
operator: OperatorName::LogicalNot,
argument: orig_test,
};
true
} else {
false
};
if swapped || cons_has_return {
let alternate_branch = alternate.take(self.ctx.session);
*maybe_alternate = None;
body.insert(i + 1, alternate_branch);
}
}
};
i += 1;
}
}
Syntax::IdentifierExpr { name } => {
self.ctx.track_variable_usage(scope, *name);
}
Syntax::IdentifierPattern { name } => {
self.ctx.track_variable_usage(scope, *name);
}
Syntax::MemberExpr {
right: p2,
optional_chaining: false,
left: NodeData {
stx: Syntax::IdentifierExpr { name: p1 },
..
},
..
} if p2.as_slice() == b"prototype" => {
if let Some(sym) = scope.find_symbol(*p1) {
self
.ctx
.symbols
.entry(sym)
.or_insert_with(|| MinifySymbol::new(self.ctx.session))
.has_prototype = true;
};
}
Syntax::UnaryExpr {
parenthesised: _,
operator: OperatorName::New,
argument,
} => {
let var_name = match &argument.stx {
Syntax::CallExpr { callee, .. } => match &callee.stx {
Syntax::IdentifierExpr { name } => Some(*name),
_ => None,
},
Syntax::IdentifierExpr { name } => Some(*name),
_ => None,
};
if let Some(var_name) = var_name {
if let Some(sym) = scope.find_symbol(var_name) {
self
.ctx
.symbols
.entry(sym)
.or_insert_with(|| MinifySymbol::new(self.ctx.session))
.is_used_as_constructor = true;
};
};
}
Syntax::JsxElement {
name:
Some(NodeData {
stx: Syntax::IdentifierExpr { name },
..
}),
..
} => {
if let Some(sym) = n.scope.find_symbol(*name) {
self
.ctx
.symbols
.entry(sym)
.or_insert_with(|| MinifySymbol::new(self.ctx.session))
.is_used_as_jsx_component = true;
};
}
_ => {}
}
}
fn on_syntax_up(&mut self, node: &mut NodeData<'a>) -> () {
let loc = node.loc;
let scope = node.scope;
match &mut node.stx {
Syntax::BinaryExpr {
operator: OperatorName::Addition,
left:
NodeData {
stx: Syntax::LiteralStringExpr { value: l },
..
},
right:
NodeData {
stx: Syntax::LiteralStringExpr { value: r },
..
},
..
} => {
let concat = self
.ctx
.session
.get_allocator()
.alloc_slice_fill_default(l.len() + r.len());
concat[..l.len()].copy_from_slice(l.as_bytes());
concat[l.len()..].copy_from_slice(r.as_bytes());
node.stx = Syntax::LiteralStringExpr {
value: unsafe { from_utf8_unchecked(concat) },
};
}
Syntax::BlockStmt { body } => {
let mut returned = false;
let mut w = 0;
let mut r = 0;
while r < body.len() {
if returned {
break;
};
let r_scope = body[r].scope;
let keep = match &mut body[r].stx {
Syntax::BlockStmt { body: block_body } => {
if block_body.is_empty() {
false
} else if r_scope.symbol_names().is_empty() {
let mut to_add = self.ctx.session.new_vec();
for s in block_body {
to_add.push(s.take(self.ctx.session));
}
body.splice(r + 1..r + 1, to_add);
false
} else {
true
}
}
Syntax::ExpressionStmt { expression: _ } => {
true
}
Syntax::ReturnStmt { .. } => {
returned = true;
true
}
_ => true,
};
if keep {
body.swap(w, r);
w += 1;
};
r += 1;
}
body.truncate(w);
}
Syntax::IfStmt {
test,
consequent,
alternate,
} => {
let cons_ok = analyse_if_branch(&consequent.stx);
let alt_ok = alternate.as_ref().map(|alt| analyse_if_branch(&alt.stx));
match (cons_ok, alt_ok) {
(true, None) => {
let closure_scope = scope.find_self_or_ancestor(|t| t.is_closure()).unwrap();
let cons_expr = process_if_branch(self.ctx.session, scope, consequent);
let min_scope = self
.ctx
.scopes
.entry(closure_scope)
.or_insert_with(|| MinifyScope::new(self.ctx.session));
min_scope
.hoisted_vars
.extend_from_slice(&cons_expr.hoisted_vars);
if cons_expr.returns {
consequent.stx = Syntax::ReturnStmt {
value: Some(cons_expr.expression),
};
} else {
let right = cons_expr.expression;
let test = test.take(self.ctx.session);
node.stx = Syntax::ExpressionStmt {
expression: new_node(self.ctx.session, scope, loc, Syntax::BinaryExpr {
parenthesised: false,
operator: OperatorName::LogicalAnd,
left: test,
right,
}),
};
}
}
(true, Some(true)) => {
let closure_scope = scope.find_self_or_ancestor(|t| t.is_closure()).unwrap();
let cons_expr = process_if_branch(self.ctx.session, scope, consequent);
let alt_expr = process_if_branch(self.ctx.session, scope, alternate.as_mut().unwrap());
let min_scope = self
.ctx
.scopes
.entry(closure_scope)
.or_insert_with(|| MinifyScope::new(self.ctx.session));
min_scope
.hoisted_vars
.extend_from_slice(&cons_expr.hoisted_vars);
min_scope
.hoisted_vars
.extend_from_slice(&alt_expr.hoisted_vars);
assert!(cons_expr.returns && alt_expr.returns);
let test = test.take(self.ctx.session);
let consequent = cons_expr.expression;
let alternate = alt_expr.expression;
node.stx = Syntax::ExpressionStmt {
expression: new_node(self.ctx.session, scope, loc, Syntax::ConditionalExpr {
parenthesised: false,
test,
consequent,
alternate,
}),
};
}
_ => {}
};
}
_ => {}
};
}
}