use super::ctx::MinifyScope;
use super::ctx::MinifySymbol;
use parse_js::ast::new_node;
use parse_js::ast::ClassOrObjectMemberKey;
use parse_js::ast::ClassOrObjectMemberValue;
use parse_js::ast::ExportNames;
use parse_js::ast::NodeData;
use parse_js::ast::ObjectMemberType;
use parse_js::ast::Syntax;
use parse_js::ast::VarDeclMode;
use parse_js::ast::VariableDeclarator;
use parse_js::flag::Flags;
use parse_js::session::Session;
use parse_js::session::SessionHashMap;
use parse_js::source::SourceRange;
use parse_js::symbol::Scope;
use parse_js::symbol::ScopeFlag;
use parse_js::symbol::Symbol;
use parse_js::visit::JourneyControls;
use parse_js::visit::Visitor;
pub struct ExportBinding<'a> {
pub target: SourceRange<'a>,
pub alias: SourceRange<'a>,
}
fn unwrap_block_statement_if_possible<'a>(session: &'a Session, node: &mut NodeData<'a>) {
if let Syntax::BlockStmt { body } = &mut node.stx {
if body.len() == 1 {
let stmt = body[0].take(session);
core::mem::swap(node, stmt);
};
};
}
pub struct Pass3<'a, 'b> {
pub session: &'a Session,
pub export_bindings: &'b mut Vec<ExportBinding<'a>>,
pub symbols: &'b mut SessionHashMap<'a, Symbol, MinifySymbol<'a>>,
pub scopes: &'b mut SessionHashMap<'a, Scope<'a>, MinifyScope<'a>>,
}
impl<'a, 'b> Pass3<'a, 'b> {
fn visit_exported_pattern(&mut self, n: &mut NodeData<'a>) -> () {
match &mut n.stx {
Syntax::ArrayPattern { elements, rest } => {
for e in elements {
if let Some(e) = e {
self.visit_exported_pattern(e.target);
}
}
if let Some(rest) = rest {
self.visit_exported_pattern(*rest);
}
}
Syntax::ObjectPattern { properties, rest } => {
for p in properties {
self.visit_exported_pattern(*p);
}
if let Some(rest) = rest {
self.visit_exported_pattern(*rest);
}
}
Syntax::ObjectPatternProperty { target, .. } => {
self.visit_exported_pattern(*target);
}
Syntax::IdentifierPattern { name } => self.export_bindings.push(ExportBinding {
target: *name,
alias: *name,
}),
_ => unreachable!(),
}
}
}
impl<'a, 'b> Visitor<'a> for Pass3<'a, 'b> {
fn on_syntax_down(&mut self, node: &mut NodeData<'a>, ctl: &mut JourneyControls) -> () {
let loc = node.loc;
let scope = node.scope;
let mut new_stx: Option<Syntax<'_>> = None;
match &mut node.stx {
Syntax::TopLevel { body } | Syntax::BlockStmt { body } => {
if scope.typ().is_closure_or_global() {
if let Some(min_scope) = self.scopes.get_mut(&scope) {
if !min_scope.hoisted_vars.is_empty() {
body.insert(
0,
new_node(self.session, scope, loc, Syntax::VarDecl {
export: false,
mode: VarDeclMode::Var,
declarators: {
let mut decls = self.session.new_vec();
for v in min_scope.hoisted_vars.iter() {
decls.push(VariableDeclarator {
pattern: new_node(self.session, scope, loc, Syntax::IdentifierPattern {
name: *v,
}),
initializer: None,
})
}
decls
},
}),
);
}
for fn_decl in min_scope.hoisted_functions.values_mut() {
body.insert(0, fn_decl.take(self.session));
}
};
};
}
Syntax::ArrowFunctionExpr {
parenthesised,
is_async,
signature,
body,
} => {
if let Syntax::BlockStmt { body } = &mut body.stx {
if body.len() == 1 {
if let Syntax::ReturnStmt { value } = &mut body[0].stx {
if let Some(return_value) = value {
new_stx = Some(Syntax::ArrowFunctionExpr {
parenthesised: *parenthesised,
is_async: *is_async,
signature: signature.take(self.session),
body: return_value.take(self.session),
});
}
};
};
};
}
Syntax::FunctionExpr {
parenthesised: _,
is_async,
generator: false,
name: None,
signature,
body,
} => {
let fn_scope = body.scope;
if !fn_scope
.flags()
.has_any(ScopeFlag::UsesArguments | ScopeFlag::UsesThis)
{
new_stx = Some(Syntax::ArrowFunctionExpr {
parenthesised: true,
is_async: *is_async,
signature: signature.take(self.session),
body: body.take(self.session),
});
};
}
Syntax::FunctionDecl {
export: false,
body,
generator: false,
is_async,
name: Some(name),
signature,
..
} => {
let fn_scope = body.scope;
if !fn_scope.flags().has_any(ScopeFlag::UsesArguments | ScopeFlag::UsesThis)
&& scope.find_symbol(name.loc).and_then(|sym| self.symbols.get(&sym)).filter(|sym| sym.is_used_as_constructor || sym.has_prototype).is_none()
{
let var_decl_pat = new_node(
self.session,
scope,
name.loc,
Syntax::IdentifierPattern { name: name.loc },
);
let var_decl_init = new_node(
self.session,
scope,
loc,
Syntax::ArrowFunctionExpr {
parenthesised: true,
is_async: *is_async,
signature: signature.take(self.session),
body: body.take(self.session),
},
);
new_stx = Some(Syntax::VarDecl {
export: false,
mode: VarDeclMode::Var,
declarators: {
let mut vec = self.session.new_vec();
vec.push(VariableDeclarator {
pattern: var_decl_pat,
initializer: Some(var_decl_init),
});
vec
},
});
}
}
Syntax::IdentifierPattern { name } => {
let sym = scope.find_symbol(*name);
if let Some(sym) = sym {
let minified = self.symbols[&sym].minified_name.unwrap();
new_stx = Some(Syntax::IdentifierPattern { name: minified });
};
}
Syntax::IdentifierExpr { name } => {
let sym = scope.find_symbol(*name);
if let Some(sym) = sym {
let minified = self.symbols[&sym].minified_name.unwrap();
new_stx = Some(Syntax::IdentifierExpr { name: minified });
};
}
Syntax::ClassOrFunctionName { name } => {
let sym = scope.find_symbol(*name);
if let Some(sym) = sym {
let minified = self.symbols[&sym].minified_name.unwrap();
new_stx = Some(Syntax::ClassOrFunctionName { name: minified });
};
}
Syntax::ClassDecl {
export: true,
export_default,
name,
..
}
| Syntax::FunctionDecl {
export: true,
export_default,
name,
..
} => {
if let Some(name) = name {
match &name.stx {
Syntax::ClassOrFunctionName { name } => {
self.export_bindings.push(ExportBinding {
target: *name,
alias: if *export_default {
SourceRange::from_slice(b"default")
} else {
*name
},
});
}
_ => unreachable!(),
};
}
}
Syntax::VarDecl {
export: true,
declarators,
..
} => {
for decl in declarators.iter_mut() {
self.visit_exported_pattern(decl.pattern);
}
}
Syntax::ExportListStmt { names, from } => {
ctl.skip();
match from {
None => match names {
ExportNames::Specific(names) => {
for e in names {
self.export_bindings.push(ExportBinding {
target: e.target,
alias: match &e.alias.stx {
Syntax::IdentifierPattern { name } => *name,
_ => unreachable!(),
},
});
new_stx = Some(Syntax::EmptyStmt {});
}
}
ExportNames::All(_) => unreachable!(),
},
_ => {}
}
}
Syntax::ObjectPatternProperty {
key: ClassOrObjectMemberKey::Direct(name),
target,
shorthand: true,
default_value,
..
} => {
if scope.find_symbol(*name).is_some() {
new_stx = Some(Syntax::ObjectPatternProperty {
key: ClassOrObjectMemberKey::Direct(*name),
target: target.take(self.session),
default_value: default_value.take(),
shorthand: false,
});
};
}
Syntax::ObjectMember {
typ: ObjectMemberType::Shorthand { identifier },
} => {
let name = identifier.loc;
if scope.find_symbol(name).is_some() {
let replacement_initializer_node = identifier.take(self.session);
new_stx = Some(Syntax::ObjectMember {
typ: ObjectMemberType::Valued {
key: ClassOrObjectMemberKey::Direct(name),
value: ClassOrObjectMemberValue::Property {
initializer: Some(replacement_initializer_node),
},
},
});
};
}
_ => {}
};
if let Some(new_stx) = new_stx {
node.stx = new_stx;
}
}
fn on_syntax_up(&mut self, node: &mut NodeData<'a>) -> () {
match &mut node.stx {
Syntax::IfStmt {
consequent,
alternate,
..
} => {
unwrap_block_statement_if_possible(self.session, consequent);
if let Some(alt) = alternate {
unwrap_block_statement_if_possible(self.session, alt);
}
}
Syntax::WhileStmt { body, .. }
| Syntax::DoWhileStmt { body, .. }
| Syntax::ForStmt { body, .. } => {
unwrap_block_statement_if_possible(self.session, body);
}
_ => {}
};
}
}