4137 lines
150 KiB
JavaScript
4137 lines
150 KiB
JavaScript
/***********************************************************************
|
||
|
||
A JavaScript tokenizer / parser / beautifier / compressor.
|
||
https://github.com/mishoo/UglifyJS2
|
||
|
||
-------------------------------- (C) ---------------------------------
|
||
|
||
Author: Mihai Bazon
|
||
<mihai.bazon@gmail.com>
|
||
http://mihai.bazon.net/blog
|
||
|
||
Distributed under the BSD license:
|
||
|
||
Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
||
|
||
Redistribution and use in source and binary forms, with or without
|
||
modification, are permitted provided that the following conditions
|
||
are met:
|
||
|
||
* Redistributions of source code must retain the above
|
||
copyright notice, this list of conditions and the following
|
||
disclaimer.
|
||
|
||
* Redistributions in binary form must reproduce the above
|
||
copyright notice, this list of conditions and the following
|
||
disclaimer in the documentation and/or other materials
|
||
provided with the distribution.
|
||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||
SUCH DAMAGE.
|
||
|
||
***********************************************************************/
|
||
|
||
import {
|
||
AST_Accessor,
|
||
AST_Array,
|
||
AST_Arrow,
|
||
AST_Assign,
|
||
AST_BigInt,
|
||
AST_Binary,
|
||
AST_Block,
|
||
AST_BlockStatement,
|
||
AST_Boolean,
|
||
AST_Break,
|
||
AST_Call,
|
||
AST_Catch,
|
||
AST_Chain,
|
||
AST_Class,
|
||
AST_ClassProperty,
|
||
AST_ClassStaticBlock,
|
||
AST_ConciseMethod,
|
||
AST_Conditional,
|
||
AST_Const,
|
||
AST_Constant,
|
||
AST_Debugger,
|
||
AST_Default,
|
||
AST_DefaultAssign,
|
||
AST_Definitions,
|
||
AST_Defun,
|
||
AST_Destructuring,
|
||
AST_Directive,
|
||
AST_Do,
|
||
AST_Dot,
|
||
AST_DotHash,
|
||
AST_DWLoop,
|
||
AST_EmptyStatement,
|
||
AST_Exit,
|
||
AST_Expansion,
|
||
AST_Export,
|
||
AST_False,
|
||
AST_For,
|
||
AST_ForIn,
|
||
AST_Function,
|
||
AST_Hole,
|
||
AST_If,
|
||
AST_Import,
|
||
AST_Infinity,
|
||
AST_LabeledStatement,
|
||
AST_Lambda,
|
||
AST_Let,
|
||
AST_NaN,
|
||
AST_New,
|
||
AST_Node,
|
||
AST_Null,
|
||
AST_Number,
|
||
AST_Object,
|
||
AST_ObjectKeyVal,
|
||
AST_ObjectProperty,
|
||
AST_PrefixedTemplateString,
|
||
AST_PropAccess,
|
||
AST_RegExp,
|
||
AST_Return,
|
||
AST_Scope,
|
||
AST_Sequence,
|
||
AST_SimpleStatement,
|
||
AST_Statement,
|
||
AST_String,
|
||
AST_Sub,
|
||
AST_Switch,
|
||
AST_SwitchBranch,
|
||
AST_Symbol,
|
||
AST_SymbolClassProperty,
|
||
AST_SymbolDeclaration,
|
||
AST_SymbolDefun,
|
||
AST_SymbolExport,
|
||
AST_SymbolFunarg,
|
||
AST_SymbolLambda,
|
||
AST_SymbolLet,
|
||
AST_SymbolMethod,
|
||
AST_SymbolRef,
|
||
AST_SymbolUsing,
|
||
AST_TemplateString,
|
||
AST_This,
|
||
AST_Toplevel,
|
||
AST_True,
|
||
AST_Try,
|
||
AST_Unary,
|
||
AST_UnaryPostfix,
|
||
AST_UnaryPrefix,
|
||
AST_Undefined,
|
||
AST_Using,
|
||
AST_Var,
|
||
AST_VarDef,
|
||
AST_While,
|
||
AST_With,
|
||
AST_Yield,
|
||
|
||
TreeTransformer,
|
||
TreeWalker,
|
||
walk,
|
||
walk_abort,
|
||
|
||
_NOINLINE,
|
||
} from "../ast.js";
|
||
import {
|
||
defaults,
|
||
HOP,
|
||
make_node,
|
||
make_void_0,
|
||
makePredicate,
|
||
MAP,
|
||
remove,
|
||
return_false,
|
||
return_true,
|
||
regexp_source_fix,
|
||
has_annotation,
|
||
regexp_is_safe,
|
||
} from "../utils/index.js";
|
||
import { first_in_statement } from "../utils/first_in_statement.js";
|
||
import { equivalent_to } from "../equivalent-to.js";
|
||
import {
|
||
is_basic_identifier_string,
|
||
JS_Parse_Error,
|
||
parse,
|
||
PRECEDENCE,
|
||
} from "../parse.js";
|
||
import { OutputStream } from "../output.js";
|
||
import { base54, format_mangler_options } from "../scope.js";
|
||
import "../size.js";
|
||
|
||
import "./evaluate.js";
|
||
import "./drop-side-effect-free.js";
|
||
import "./drop-unused.js";
|
||
import "./reduce-vars.js";
|
||
import {
|
||
is_undeclared_ref,
|
||
bitwise_binop,
|
||
lazy_op,
|
||
is_nullish,
|
||
is_undefined,
|
||
is_lhs,
|
||
aborts,
|
||
is_used_in_expression,
|
||
} from "./inference.js";
|
||
import {
|
||
SQUEEZED,
|
||
OPTIMIZED,
|
||
CLEAR_BETWEEN_PASSES,
|
||
TOP,
|
||
UNDEFINED,
|
||
UNUSED,
|
||
TRUTHY,
|
||
FALSY,
|
||
has_flag,
|
||
set_flag,
|
||
clear_flag,
|
||
} from "./compressor-flags.js";
|
||
import {
|
||
make_sequence,
|
||
best_of,
|
||
best_of_expression,
|
||
make_empty_function,
|
||
make_node_from_constant,
|
||
merge_sequence,
|
||
get_simple_key,
|
||
has_break_or_continue,
|
||
maintain_this_binding,
|
||
is_empty,
|
||
is_identifier_atom,
|
||
is_reachable,
|
||
can_be_evicted_from_block,
|
||
as_statement_array,
|
||
is_func_expr,
|
||
} from "./common.js";
|
||
import { tighten_body, extract_from_unreachable_code } from "./tighten-body.js";
|
||
import { inline_into_symbolref, inline_into_call } from "./inline.js";
|
||
import "./global-defs.js";
|
||
import { is_pure_native_fn, is_pure_native_method, is_pure_native_static_fn, is_pure_native_static_property, pure_access_globals } from "./native-objects.js";
|
||
|
||
class Compressor extends TreeWalker {
|
||
constructor(options, { false_by_default = false, mangle_options = false }) {
|
||
super();
|
||
if (options.defaults !== undefined && !options.defaults) false_by_default = true;
|
||
this.options = defaults(options, {
|
||
arguments : false,
|
||
arrows : !false_by_default,
|
||
booleans : !false_by_default,
|
||
booleans_as_integers : false,
|
||
collapse_vars : !false_by_default,
|
||
comparisons : !false_by_default,
|
||
computed_props: !false_by_default,
|
||
conditionals : !false_by_default,
|
||
dead_code : !false_by_default,
|
||
defaults : true,
|
||
directives : !false_by_default,
|
||
drop_console : false,
|
||
drop_debugger : !false_by_default,
|
||
ecma : 5,
|
||
builtins_ecma : 5,
|
||
builtins_pure : false,
|
||
evaluate : !false_by_default,
|
||
expression : false,
|
||
global_defs : false,
|
||
hoist_funs : false,
|
||
hoist_props : !false_by_default,
|
||
hoist_vars : false,
|
||
ie8 : false,
|
||
if_return : !false_by_default,
|
||
inline : !false_by_default,
|
||
join_vars : !false_by_default,
|
||
keep_classnames: false,
|
||
keep_fargs : true,
|
||
keep_fnames : false,
|
||
keep_infinity : false,
|
||
lhs_constants : !false_by_default,
|
||
loops : !false_by_default,
|
||
module : false,
|
||
negate_iife : !false_by_default,
|
||
passes : 1,
|
||
properties : !false_by_default,
|
||
pure_getters : !false_by_default && "strict",
|
||
pure_funcs : null,
|
||
pure_new : false,
|
||
reduce_funcs : !false_by_default,
|
||
reduce_vars : !false_by_default,
|
||
sequences : !false_by_default,
|
||
side_effects : !false_by_default,
|
||
switches : !false_by_default,
|
||
top_retain : null,
|
||
toplevel : !!(options && options["top_retain"]),
|
||
typeofs : !false_by_default,
|
||
unsafe : false,
|
||
unsafe_arrows : false,
|
||
unsafe_comps : false,
|
||
unsafe_Function: false,
|
||
unsafe_math : false,
|
||
unsafe_symbols: false,
|
||
unsafe_methods: false,
|
||
unsafe_proto : false,
|
||
unsafe_regexp : false,
|
||
unsafe_undefined: false,
|
||
unused : !false_by_default,
|
||
warnings : false // legacy
|
||
}, true);
|
||
var global_defs = this.options["global_defs"];
|
||
if (typeof global_defs == "object") for (var key in global_defs) {
|
||
if (key[0] === "@" && HOP(global_defs, key)) {
|
||
global_defs[key.slice(1)] = parse(global_defs[key], {
|
||
expression: true
|
||
});
|
||
}
|
||
}
|
||
if (this.options["inline"] === true) this.options["inline"] = 3;
|
||
var pure_funcs = this.options["pure_funcs"];
|
||
if (typeof pure_funcs == "function") {
|
||
this.pure_funcs = pure_funcs;
|
||
} else {
|
||
this.pure_funcs = pure_funcs ? function(node) {
|
||
return !pure_funcs.includes(node.expression.print_to_string());
|
||
} : return_true;
|
||
}
|
||
var top_retain = this.options["top_retain"];
|
||
if (top_retain instanceof RegExp) {
|
||
this.top_retain = function(def) {
|
||
return top_retain.test(def.name);
|
||
};
|
||
} else if (typeof top_retain == "function") {
|
||
this.top_retain = top_retain;
|
||
} else if (top_retain) {
|
||
if (typeof top_retain == "string") {
|
||
top_retain = top_retain.split(/,/);
|
||
}
|
||
this.top_retain = function(def) {
|
||
return top_retain.includes(def.name);
|
||
};
|
||
}
|
||
if (this.options["module"]) {
|
||
this.directives["use strict"] = true;
|
||
this.options["toplevel"] = true;
|
||
}
|
||
var toplevel = this.options["toplevel"];
|
||
this.toplevel = typeof toplevel == "string" ? {
|
||
funcs: /funcs/.test(toplevel),
|
||
vars: /vars/.test(toplevel)
|
||
} : {
|
||
funcs: toplevel,
|
||
vars: toplevel
|
||
};
|
||
var sequences = this.options["sequences"];
|
||
this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
|
||
this.evaluated_regexps = new Map();
|
||
this._toplevel = undefined;
|
||
this._mangle_options = mangle_options
|
||
? format_mangler_options(mangle_options)
|
||
: mangle_options;
|
||
|
||
this.pure_access_globals = pure_access_globals(this);
|
||
this.is_pure_native_fn = is_pure_native_fn(this);
|
||
this.is_pure_native_method = is_pure_native_method(this);
|
||
this.is_pure_native_static_fn = is_pure_native_static_fn(this);
|
||
this.is_pure_native_static_property = is_pure_native_static_property(this);
|
||
}
|
||
|
||
mangle_options() {
|
||
var nth_identifier = this._mangle_options && this._mangle_options.nth_identifier || base54;
|
||
var module = this._mangle_options && this._mangle_options.module || this.option("module");
|
||
return { ie8: this.option("ie8"), nth_identifier, module };
|
||
}
|
||
|
||
option(key) {
|
||
return this.options[key];
|
||
}
|
||
|
||
exposed(def) {
|
||
if (def.export) return true;
|
||
if (def.global) for (var i = 0, len = def.orig.length; i < len; i++)
|
||
if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
in_boolean_context() {
|
||
if (!this.option("booleans")) return false;
|
||
var self = this.self();
|
||
for (var i = 0, p; p = this.parent(i); i++) {
|
||
if (p instanceof AST_SimpleStatement
|
||
|| p instanceof AST_Conditional && p.condition === self
|
||
|| p instanceof AST_DWLoop && p.condition === self
|
||
|| p instanceof AST_For && p.condition === self
|
||
|| p instanceof AST_If && p.condition === self
|
||
|| p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
|
||
return true;
|
||
}
|
||
if (
|
||
p instanceof AST_Binary
|
||
&& (
|
||
p.operator == "&&"
|
||
|| p.operator == "||"
|
||
|| p.operator == "??"
|
||
)
|
||
|| p instanceof AST_Conditional
|
||
|| p.tail_node() === self
|
||
) {
|
||
self = p;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
/** True if compressor.self()'s result will be turned into a 32-bit integer.
|
||
* ex:
|
||
* ~{expr}
|
||
* (1, 2, {expr}) | 0
|
||
**/
|
||
in_32_bit_context(other_operand_must_be_number) {
|
||
if (!this.option("evaluate")) return false;
|
||
var self = this.self();
|
||
for (var i = 0, p; p = this.parent(i); i++) {
|
||
if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) {
|
||
if (other_operand_must_be_number) {
|
||
return (self === p.left ? p.right : p.left).is_number(this);
|
||
} else {
|
||
return true;
|
||
}
|
||
}
|
||
if (p instanceof AST_UnaryPrefix) {
|
||
return p.operator === "~";
|
||
}
|
||
if (
|
||
p instanceof AST_Binary
|
||
&& (
|
||
// Don't talk about p.left. Can change branch taken
|
||
p.operator == "&&" && p.right === self
|
||
|| p.operator == "||" && p.right === self
|
||
|| p.operator == "??" && p.right === self
|
||
)
|
||
|| p instanceof AST_Conditional && p.condition !== self
|
||
|| p.tail_node() === self
|
||
) {
|
||
self = p;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
in_computed_key() {
|
||
if (!this.option("evaluate")) return false;
|
||
var self = this.self();
|
||
for (var i = 0, p; p = this.parent(i); i++) {
|
||
if (p instanceof AST_ObjectProperty && p.key === self) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
get_toplevel() {
|
||
return this._toplevel;
|
||
}
|
||
|
||
compress(toplevel) {
|
||
toplevel = toplevel.resolve_defines(this);
|
||
this._toplevel = toplevel;
|
||
if (this.option("expression")) {
|
||
this._toplevel.process_expression(true);
|
||
}
|
||
var passes = +this.options.passes || 1;
|
||
var min_count = 1 / 0;
|
||
var stopping = false;
|
||
var mangle = this.mangle_options();
|
||
for (var pass = 0; pass < passes; pass++) {
|
||
this._toplevel.figure_out_scope(mangle);
|
||
if (pass === 0 && this.option("drop_console")) {
|
||
// must be run before reduce_vars and compress pass
|
||
this._toplevel = this._toplevel.drop_console(this.option("drop_console"));
|
||
}
|
||
if (pass > 0 || this.option("reduce_vars")) {
|
||
this._toplevel.reset_opt_flags(this);
|
||
}
|
||
this._toplevel = this._toplevel.transform(this);
|
||
if (passes > 1) {
|
||
let count = 0;
|
||
walk(this._toplevel, () => { count++; });
|
||
if (count < min_count) {
|
||
min_count = count;
|
||
stopping = false;
|
||
} else if (stopping) {
|
||
break;
|
||
} else {
|
||
stopping = true;
|
||
}
|
||
}
|
||
}
|
||
if (this.option("expression")) {
|
||
this._toplevel.process_expression(false);
|
||
}
|
||
toplevel = this._toplevel;
|
||
this._toplevel = undefined;
|
||
return toplevel;
|
||
}
|
||
|
||
before(node, descend) {
|
||
if (has_flag(node, SQUEEZED)) return node;
|
||
var was_scope = false;
|
||
if (node instanceof AST_Scope) {
|
||
node = node.hoist_properties(this);
|
||
node = node.hoist_declarations(this);
|
||
was_scope = true;
|
||
}
|
||
// Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize()
|
||
// would call AST_Node.transform() if a different instance of AST_Node is
|
||
// produced after def_optimize().
|
||
// This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
|
||
// Migrate and defer all children's AST_Node.transform() to below, which
|
||
// will now happen after this parent AST_Node has been properly substituted
|
||
// thus gives a consistent AST snapshot.
|
||
descend(node, this);
|
||
// Existing code relies on how AST_Node.optimize() worked, and omitting the
|
||
// following replacement call would result in degraded efficiency of both
|
||
// output and performance.
|
||
descend(node, this);
|
||
var opt = node.optimize(this);
|
||
if (was_scope && opt instanceof AST_Scope) {
|
||
opt.drop_unused(this);
|
||
descend(opt, this);
|
||
}
|
||
if (opt === node) set_flag(opt, SQUEEZED);
|
||
return opt;
|
||
}
|
||
|
||
/** Alternative to plain is_lhs() which doesn't work within .optimize() */
|
||
is_lhs() {
|
||
const self = this.stack[this.stack.length - 1];
|
||
const parent = this.stack[this.stack.length - 2];
|
||
return is_lhs(self, parent);
|
||
}
|
||
}
|
||
|
||
|
||
function def_optimize(node, optimizer) {
|
||
node.DEFMETHOD("optimize", function(compressor) {
|
||
var self = this;
|
||
if (has_flag(self, OPTIMIZED)) return self;
|
||
if (compressor.has_directive("use asm")) return self;
|
||
var opt = optimizer(self, compressor);
|
||
set_flag(opt, OPTIMIZED);
|
||
return opt;
|
||
});
|
||
}
|
||
|
||
def_optimize(AST_Node, function(self) {
|
||
return self;
|
||
});
|
||
|
||
AST_Toplevel.DEFMETHOD("drop_console", function(options) {
|
||
const isArray = Array.isArray(options);
|
||
const tt = new TreeTransformer(function(self) {
|
||
if (self.TYPE !== "Call") {
|
||
return;
|
||
}
|
||
|
||
var exp = self.expression;
|
||
|
||
if (!(exp instanceof AST_PropAccess)) {
|
||
return;
|
||
}
|
||
|
||
var name = exp.expression;
|
||
var property = exp.property;
|
||
var depth = 2;
|
||
while (name.expression) {
|
||
property = name.property;
|
||
name = name.expression;
|
||
depth++;
|
||
}
|
||
|
||
if (isArray && !options.includes(property)) {
|
||
return;
|
||
}
|
||
|
||
if (is_undeclared_ref(name) && name.name == "console") {
|
||
if (
|
||
depth === 3
|
||
&& !["call", "apply"].includes(exp.property)
|
||
&& is_used_in_expression(tt)
|
||
) {
|
||
// a (used) call to Function.prototype methods (eg: console.log.bind(console))
|
||
// but not .call and .apply which would also return undefined.
|
||
exp.expression = make_empty_function(self);
|
||
set_flag(exp.expression, SQUEEZED);
|
||
self.args = [];
|
||
} else {
|
||
return make_void_0(self);
|
||
}
|
||
}
|
||
});
|
||
|
||
return this.transform(tt);
|
||
});
|
||
|
||
AST_Node.DEFMETHOD("equivalent_to", function(node) {
|
||
return equivalent_to(this, node);
|
||
});
|
||
|
||
AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
|
||
var self = this;
|
||
var tt = new TreeTransformer(function(node) {
|
||
if (insert && node instanceof AST_SimpleStatement) {
|
||
return make_node(AST_Return, node, {
|
||
value: node.body
|
||
});
|
||
}
|
||
if (!insert && node instanceof AST_Return) {
|
||
if (compressor) {
|
||
var value = node.value && node.value.drop_side_effect_free(compressor, true);
|
||
return value
|
||
? make_node(AST_SimpleStatement, node, { body: value })
|
||
: make_node(AST_EmptyStatement, node);
|
||
}
|
||
return make_node(AST_SimpleStatement, node, {
|
||
body: node.value || make_void_0(node)
|
||
});
|
||
}
|
||
if (node instanceof AST_Class || node instanceof AST_Lambda && node !== self) {
|
||
return node;
|
||
}
|
||
if (node instanceof AST_Block) {
|
||
var index = node.body.length - 1;
|
||
if (index >= 0) {
|
||
node.body[index] = node.body[index].transform(tt);
|
||
}
|
||
} else if (node instanceof AST_If) {
|
||
node.body = node.body.transform(tt);
|
||
if (node.alternative) {
|
||
node.alternative = node.alternative.transform(tt);
|
||
}
|
||
} else if (node instanceof AST_With) {
|
||
node.body = node.body.transform(tt);
|
||
}
|
||
return node;
|
||
});
|
||
self.transform(tt);
|
||
});
|
||
|
||
AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) {
|
||
const self = this;
|
||
const reduce_vars = compressor.option("reduce_vars");
|
||
|
||
const preparation = new TreeWalker(function(node, descend) {
|
||
clear_flag(node, CLEAR_BETWEEN_PASSES);
|
||
if (reduce_vars) {
|
||
if (compressor.top_retain
|
||
&& node instanceof AST_Defun // Only functions are retained
|
||
&& preparation.parent() === self
|
||
) {
|
||
set_flag(node, TOP);
|
||
}
|
||
return node.reduce_vars(preparation, descend, compressor);
|
||
}
|
||
});
|
||
// Stack of look-up tables to keep track of whether a `SymbolDef` has been
|
||
// properly assigned before use:
|
||
// - `push()` & `pop()` when visiting conditional branches
|
||
preparation.safe_ids = Object.create(null);
|
||
preparation.in_loop = null;
|
||
preparation.loop_ids = new Map();
|
||
preparation.defs_to_safe_ids = new Map();
|
||
self.walk(preparation);
|
||
});
|
||
|
||
AST_Symbol.DEFMETHOD("fixed_value", function() {
|
||
var fixed = this.thedef.fixed;
|
||
if (!fixed || fixed instanceof AST_Node) return fixed;
|
||
return fixed();
|
||
});
|
||
|
||
AST_SymbolRef.DEFMETHOD("is_immutable", function() {
|
||
var orig = this.definition().orig;
|
||
return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
|
||
});
|
||
|
||
function find_variable(compressor, name) {
|
||
var scope, i = 0;
|
||
while (scope = compressor.parent(i++)) {
|
||
if (scope instanceof AST_Scope) break;
|
||
if (scope instanceof AST_Catch && scope.argname) {
|
||
scope = scope.argname.definition().scope;
|
||
break;
|
||
}
|
||
}
|
||
return scope.find_variable(name);
|
||
}
|
||
|
||
AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
|
||
return !this.definition().undeclared
|
||
|| (compressor.option("unsafe") || compressor.option("builtins_pure")) && compressor.pure_access_globals(this.name);
|
||
});
|
||
|
||
/* -----[ optimizers ]----- */
|
||
|
||
var directives = new Set(["use asm", "use strict"]);
|
||
def_optimize(AST_Directive, function(self, compressor) {
|
||
if (compressor.option("directives")
|
||
&& (!directives.has(self.value) || compressor.has_directive(self.value) !== self)) {
|
||
return make_node(AST_EmptyStatement, self);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Debugger, function(self, compressor) {
|
||
if (compressor.option("drop_debugger"))
|
||
return make_node(AST_EmptyStatement, self);
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_LabeledStatement, function(self, compressor) {
|
||
if (self.body instanceof AST_Break
|
||
&& compressor.loopcontrol_target(self.body) === self.body) {
|
||
return make_node(AST_EmptyStatement, self);
|
||
}
|
||
return self.label.references.length == 0 ? self.body : self;
|
||
});
|
||
|
||
def_optimize(AST_Block, function(self, compressor) {
|
||
tighten_body(self.body, compressor);
|
||
return self;
|
||
});
|
||
|
||
function can_be_extracted_from_if_block(node) {
|
||
return !(
|
||
node instanceof AST_Const
|
||
|| node instanceof AST_Let
|
||
|| node instanceof AST_Using
|
||
|| node instanceof AST_Class
|
||
);
|
||
}
|
||
|
||
def_optimize(AST_BlockStatement, function(self, compressor) {
|
||
tighten_body(self.body, compressor);
|
||
switch (self.body.length) {
|
||
case 1:
|
||
if (!compressor.has_directive("use strict")
|
||
&& compressor.parent() instanceof AST_If
|
||
&& can_be_extracted_from_if_block(self.body[0])
|
||
|| can_be_evicted_from_block(self.body[0])) {
|
||
return self.body[0];
|
||
}
|
||
break;
|
||
case 0: return make_node(AST_EmptyStatement, self);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
function opt_AST_Lambda(self, compressor) {
|
||
tighten_body(self.body, compressor);
|
||
if (compressor.option("side_effects")
|
||
&& self.body.length == 1
|
||
&& self.body[0] === compressor.has_directive("use strict")) {
|
||
self.body.length = 0;
|
||
}
|
||
return self;
|
||
}
|
||
def_optimize(AST_Lambda, opt_AST_Lambda);
|
||
|
||
AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) {
|
||
var self = this;
|
||
if (compressor.has_directive("use asm")) return self;
|
||
|
||
var hoist_funs = compressor.option("hoist_funs");
|
||
var hoist_vars = compressor.option("hoist_vars");
|
||
|
||
if (hoist_funs || hoist_vars) {
|
||
var dirs = [];
|
||
var hoisted = [];
|
||
var vars = new Map(), vars_found = 0, var_decl = 0;
|
||
// let's count var_decl first, we seem to waste a lot of
|
||
// space if we hoist `var` when there's only one.
|
||
walk(self, node => {
|
||
if (node instanceof AST_Scope && node !== self)
|
||
return true;
|
||
if (node instanceof AST_Var) {
|
||
++var_decl;
|
||
return true;
|
||
}
|
||
});
|
||
hoist_vars = hoist_vars && var_decl > 1;
|
||
var tt = new TreeTransformer(
|
||
function before(node) {
|
||
if (node !== self) {
|
||
if (node instanceof AST_Directive) {
|
||
dirs.push(node);
|
||
return make_node(AST_EmptyStatement, node);
|
||
}
|
||
if (hoist_funs && node instanceof AST_Defun
|
||
&& !(tt.parent() instanceof AST_Export)
|
||
&& tt.parent() === self) {
|
||
hoisted.push(node);
|
||
return make_node(AST_EmptyStatement, node);
|
||
}
|
||
if (
|
||
hoist_vars
|
||
&& node instanceof AST_Var
|
||
&& !node.definitions.some(def => def.name instanceof AST_Destructuring)
|
||
) {
|
||
node.definitions.forEach(function(def) {
|
||
vars.set(def.name.name, def);
|
||
++vars_found;
|
||
});
|
||
var seq = node.to_assignments(compressor);
|
||
var p = tt.parent();
|
||
if (p instanceof AST_ForIn && p.init === node) {
|
||
if (seq == null) {
|
||
var def = node.definitions[0].name;
|
||
return make_node(AST_SymbolRef, def, def);
|
||
}
|
||
return seq;
|
||
}
|
||
if (p instanceof AST_For && p.init === node) {
|
||
return seq;
|
||
}
|
||
if (!seq) return make_node(AST_EmptyStatement, node);
|
||
return make_node(AST_SimpleStatement, node, {
|
||
body: seq
|
||
});
|
||
}
|
||
if (node instanceof AST_Scope)
|
||
return node; // to avoid descending in nested scopes
|
||
}
|
||
}
|
||
);
|
||
self = self.transform(tt);
|
||
if (vars_found > 0) {
|
||
// collect only vars which don't show up in self's arguments list
|
||
var defs = [];
|
||
const is_lambda = self instanceof AST_Lambda;
|
||
const args_as_names = is_lambda ? self.args_as_names() : null;
|
||
vars.forEach((def, name) => {
|
||
if (is_lambda && args_as_names.some((x) => x.name === def.name.name)) {
|
||
vars.delete(name);
|
||
} else {
|
||
def = def.clone();
|
||
def.value = null;
|
||
defs.push(def);
|
||
vars.set(name, def);
|
||
}
|
||
});
|
||
if (defs.length > 0) {
|
||
// try to merge in assignments
|
||
for (var i = 0; i < self.body.length;) {
|
||
if (self.body[i] instanceof AST_SimpleStatement) {
|
||
var expr = self.body[i].body, sym, assign;
|
||
if (expr instanceof AST_Assign
|
||
&& expr.operator == "="
|
||
&& (sym = expr.left) instanceof AST_Symbol
|
||
&& vars.has(sym.name)
|
||
) {
|
||
var def = vars.get(sym.name);
|
||
if (def.value) break;
|
||
def.value = expr.right;
|
||
remove(defs, def);
|
||
defs.push(def);
|
||
self.body.splice(i, 1);
|
||
continue;
|
||
}
|
||
if (expr instanceof AST_Sequence
|
||
&& (assign = expr.expressions[0]) instanceof AST_Assign
|
||
&& assign.operator == "="
|
||
&& (sym = assign.left) instanceof AST_Symbol
|
||
&& vars.has(sym.name)
|
||
) {
|
||
var def = vars.get(sym.name);
|
||
if (def.value) break;
|
||
def.value = assign.right;
|
||
remove(defs, def);
|
||
defs.push(def);
|
||
self.body[i].body = make_sequence(expr, expr.expressions.slice(1));
|
||
continue;
|
||
}
|
||
}
|
||
if (self.body[i] instanceof AST_EmptyStatement) {
|
||
self.body.splice(i, 1);
|
||
continue;
|
||
}
|
||
if (self.body[i] instanceof AST_BlockStatement) {
|
||
self.body.splice(i, 1, ...self.body[i].body);
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
defs = make_node(AST_Var, self, {
|
||
definitions: defs
|
||
});
|
||
hoisted.push(defs);
|
||
}
|
||
}
|
||
self.body = dirs.concat(hoisted, self.body);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
AST_Scope.DEFMETHOD("hoist_properties", function(compressor) {
|
||
var self = this;
|
||
if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
|
||
var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false;
|
||
var defs_by_id = new Map();
|
||
var hoister = new TreeTransformer(function(node, descend) {
|
||
if (node instanceof AST_VarDef) {
|
||
const sym = node.name;
|
||
let def;
|
||
let value;
|
||
if (sym.scope === self
|
||
&& !(sym instanceof AST_SymbolUsing)
|
||
&& (def = sym.definition()).escaped != 1
|
||
&& !def.assignments
|
||
&& !def.direct_access
|
||
&& !def.single_use
|
||
&& !compressor.exposed(def)
|
||
&& !top_retain(def)
|
||
&& (value = sym.fixed_value()) === node.value
|
||
&& value instanceof AST_Object
|
||
&& !value.properties.some(prop =>
|
||
prop instanceof AST_Expansion || prop.computed_key()
|
||
)
|
||
) {
|
||
descend(node, this);
|
||
const defs = new Map();
|
||
const assignments = [];
|
||
value.properties.forEach(({ key, value }) => {
|
||
const scope = hoister.find_scope();
|
||
const symbol = self.create_symbol(sym.CTOR, {
|
||
source: sym,
|
||
scope,
|
||
conflict_scopes: new Set([
|
||
scope,
|
||
...sym.definition().references.map(ref => ref.scope)
|
||
]),
|
||
tentative_name: sym.name + "_" + key
|
||
});
|
||
|
||
defs.set(String(key), symbol.definition());
|
||
|
||
assignments.push(make_node(AST_VarDef, node, {
|
||
name: symbol,
|
||
value
|
||
}));
|
||
});
|
||
defs_by_id.set(def.id, defs);
|
||
return MAP.splice(assignments);
|
||
}
|
||
} else if (node instanceof AST_PropAccess
|
||
&& node.expression instanceof AST_SymbolRef
|
||
) {
|
||
const defs = defs_by_id.get(node.expression.definition().id);
|
||
if (defs) {
|
||
const def = defs.get(String(get_simple_key(node.property)));
|
||
const sym = make_node(AST_SymbolRef, node, {
|
||
name: def.name,
|
||
scope: node.expression.scope,
|
||
thedef: def
|
||
});
|
||
sym.reference({});
|
||
return sym;
|
||
}
|
||
}
|
||
});
|
||
return self.transform(hoister);
|
||
});
|
||
|
||
def_optimize(AST_SimpleStatement, function(self, compressor) {
|
||
if (compressor.option("side_effects")) {
|
||
var body = self.body;
|
||
var node = body.drop_side_effect_free(compressor, true);
|
||
if (!node) {
|
||
return make_node(AST_EmptyStatement, self);
|
||
}
|
||
if (node !== body) {
|
||
return make_node(AST_SimpleStatement, self, { body: node });
|
||
}
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_While, function(self, compressor) {
|
||
return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self;
|
||
});
|
||
|
||
def_optimize(AST_Do, function(self, compressor) {
|
||
if (!compressor.option("loops")) return self;
|
||
var cond = self.condition.tail_node().evaluate(compressor);
|
||
if (!(cond instanceof AST_Node)) {
|
||
if (cond) return make_node(AST_For, self, {
|
||
body: make_node(AST_BlockStatement, self.body, {
|
||
body: [
|
||
self.body,
|
||
make_node(AST_SimpleStatement, self.condition, {
|
||
body: self.condition
|
||
})
|
||
]
|
||
})
|
||
}).optimize(compressor);
|
||
if (!has_break_or_continue(self, compressor.parent())) {
|
||
return make_node(AST_BlockStatement, self.body, {
|
||
body: [
|
||
self.body,
|
||
make_node(AST_SimpleStatement, self.condition, {
|
||
body: self.condition
|
||
})
|
||
]
|
||
}).optimize(compressor);
|
||
}
|
||
}
|
||
return self;
|
||
});
|
||
|
||
function if_break_in_loop(self, compressor) {
|
||
var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
|
||
if (compressor.option("dead_code") && is_break(first)) {
|
||
var body = [];
|
||
if (self.init instanceof AST_Statement) {
|
||
body.push(self.init);
|
||
} else if (self.init) {
|
||
body.push(make_node(AST_SimpleStatement, self.init, {
|
||
body: self.init
|
||
}));
|
||
}
|
||
if (self.condition) {
|
||
body.push(make_node(AST_SimpleStatement, self.condition, {
|
||
body: self.condition
|
||
}));
|
||
}
|
||
extract_from_unreachable_code(compressor, self.body, body);
|
||
return make_node(AST_BlockStatement, self, {
|
||
body: body
|
||
});
|
||
}
|
||
if (first instanceof AST_If) {
|
||
if (is_break(first.body)) {
|
||
if (self.condition) {
|
||
self.condition = make_node(AST_Binary, self.condition, {
|
||
left: self.condition,
|
||
operator: "&&",
|
||
right: first.condition.negate(compressor),
|
||
});
|
||
} else {
|
||
self.condition = first.condition.negate(compressor);
|
||
}
|
||
drop_it(first.alternative);
|
||
} else if (is_break(first.alternative)) {
|
||
if (self.condition) {
|
||
self.condition = make_node(AST_Binary, self.condition, {
|
||
left: self.condition,
|
||
operator: "&&",
|
||
right: first.condition,
|
||
});
|
||
} else {
|
||
self.condition = first.condition;
|
||
}
|
||
drop_it(first.body);
|
||
}
|
||
}
|
||
return self;
|
||
|
||
function is_break(node) {
|
||
return node instanceof AST_Break
|
||
&& compressor.loopcontrol_target(node) === compressor.self();
|
||
}
|
||
|
||
function drop_it(rest) {
|
||
rest = as_statement_array(rest);
|
||
if (self.body instanceof AST_BlockStatement) {
|
||
self.body = self.body.clone();
|
||
self.body.body = rest.concat(self.body.body.slice(1));
|
||
self.body = self.body.transform(compressor);
|
||
} else {
|
||
self.body = make_node(AST_BlockStatement, self.body, {
|
||
body: rest
|
||
}).transform(compressor);
|
||
}
|
||
self = if_break_in_loop(self, compressor);
|
||
}
|
||
}
|
||
|
||
def_optimize(AST_For, function(self, compressor) {
|
||
if (!compressor.option("loops")) return self;
|
||
if (compressor.option("side_effects") && self.init) {
|
||
self.init = self.init.drop_side_effect_free(compressor);
|
||
}
|
||
if (self.condition) {
|
||
var cond = self.condition.evaluate(compressor);
|
||
if (!(cond instanceof AST_Node)) {
|
||
if (cond) self.condition = null;
|
||
else if (!compressor.option("dead_code")) {
|
||
var orig = self.condition;
|
||
self.condition = make_node_from_constant(cond, self.condition);
|
||
self.condition = best_of_expression(self.condition.transform(compressor), orig);
|
||
}
|
||
}
|
||
if (compressor.option("dead_code")) {
|
||
if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
|
||
if (!cond) {
|
||
var body = [];
|
||
extract_from_unreachable_code(compressor, self.body, body);
|
||
if (self.init instanceof AST_Statement) {
|
||
body.push(self.init);
|
||
} else if (self.init) {
|
||
body.push(make_node(AST_SimpleStatement, self.init, {
|
||
body: self.init
|
||
}));
|
||
}
|
||
body.push(make_node(AST_SimpleStatement, self.condition, {
|
||
body: self.condition
|
||
}));
|
||
return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
|
||
}
|
||
}
|
||
}
|
||
return if_break_in_loop(self, compressor);
|
||
});
|
||
|
||
def_optimize(AST_If, function(self, compressor) {
|
||
if (is_empty(self.alternative)) self.alternative = null;
|
||
|
||
if (!compressor.option("conditionals")) return self;
|
||
// if condition can be statically determined, drop
|
||
// one of the blocks. note, statically determined implies
|
||
// “has no side effects”; also it doesn't work for cases like
|
||
// `x && true`, though it probably should.
|
||
var cond = self.condition.evaluate(compressor);
|
||
if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) {
|
||
var orig = self.condition;
|
||
self.condition = make_node_from_constant(cond, orig);
|
||
self.condition = best_of_expression(self.condition.transform(compressor), orig);
|
||
}
|
||
if (compressor.option("dead_code")) {
|
||
if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
|
||
if (!cond) {
|
||
var body = [];
|
||
extract_from_unreachable_code(compressor, self.body, body);
|
||
body.push(make_node(AST_SimpleStatement, self.condition, {
|
||
body: self.condition
|
||
}));
|
||
if (self.alternative) body.push(self.alternative);
|
||
return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
|
||
} else if (!(cond instanceof AST_Node)) {
|
||
var body = [];
|
||
body.push(make_node(AST_SimpleStatement, self.condition, {
|
||
body: self.condition
|
||
}));
|
||
body.push(self.body);
|
||
if (self.alternative) {
|
||
extract_from_unreachable_code(compressor, self.alternative, body);
|
||
}
|
||
return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
|
||
}
|
||
}
|
||
var negated = self.condition.negate(compressor);
|
||
var self_condition_length = self.condition.size();
|
||
var negated_length = negated.size();
|
||
var negated_is_best = negated_length < self_condition_length;
|
||
if (self.alternative && negated_is_best) {
|
||
negated_is_best = false; // because we already do the switch here.
|
||
// no need to swap values of self_condition_length and negated_length
|
||
// here because they are only used in an equality comparison later on.
|
||
self.condition = negated;
|
||
var tmp = self.body;
|
||
self.body = self.alternative || make_node(AST_EmptyStatement, self);
|
||
self.alternative = tmp;
|
||
}
|
||
if (is_empty(self.body) && is_empty(self.alternative)) {
|
||
return make_node(AST_SimpleStatement, self.condition, {
|
||
body: self.condition.clone()
|
||
}).optimize(compressor);
|
||
}
|
||
if (self.body instanceof AST_SimpleStatement
|
||
&& self.alternative instanceof AST_SimpleStatement) {
|
||
return make_node(AST_SimpleStatement, self, {
|
||
body: make_node(AST_Conditional, self, {
|
||
condition : self.condition,
|
||
consequent : self.body.body,
|
||
alternative : self.alternative.body
|
||
})
|
||
}).optimize(compressor);
|
||
}
|
||
if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
|
||
if (self_condition_length === negated_length && !negated_is_best
|
||
&& self.condition instanceof AST_Binary && self.condition.operator == "||") {
|
||
// although the code length of self.condition and negated are the same,
|
||
// negated does not require additional surrounding parentheses.
|
||
// see https://github.com/mishoo/UglifyJS2/issues/979
|
||
negated_is_best = true;
|
||
}
|
||
if (negated_is_best) return make_node(AST_SimpleStatement, self, {
|
||
body: make_node(AST_Binary, self, {
|
||
operator : "||",
|
||
left : negated,
|
||
right : self.body.body
|
||
})
|
||
}).optimize(compressor);
|
||
return make_node(AST_SimpleStatement, self, {
|
||
body: make_node(AST_Binary, self, {
|
||
operator : "&&",
|
||
left : self.condition,
|
||
right : self.body.body
|
||
})
|
||
}).optimize(compressor);
|
||
}
|
||
if (self.body instanceof AST_EmptyStatement
|
||
&& self.alternative instanceof AST_SimpleStatement) {
|
||
return make_node(AST_SimpleStatement, self, {
|
||
body: make_node(AST_Binary, self, {
|
||
operator : "||",
|
||
left : self.condition,
|
||
right : self.alternative.body
|
||
})
|
||
}).optimize(compressor);
|
||
}
|
||
if (self.body instanceof AST_Exit
|
||
&& self.alternative instanceof AST_Exit
|
||
&& self.body.TYPE == self.alternative.TYPE) {
|
||
return make_node(self.body.CTOR, self, {
|
||
value: make_node(AST_Conditional, self, {
|
||
condition : self.condition,
|
||
consequent : self.body.value || make_void_0(self.body),
|
||
alternative : self.alternative.value || make_void_0(self.alternative),
|
||
}).transform(compressor)
|
||
}).optimize(compressor);
|
||
}
|
||
if (self.body instanceof AST_If
|
||
&& !self.body.alternative
|
||
&& !self.alternative) {
|
||
self = make_node(AST_If, self, {
|
||
condition: make_node(AST_Binary, self.condition, {
|
||
operator: "&&",
|
||
left: self.condition,
|
||
right: self.body.condition
|
||
}),
|
||
body: self.body.body,
|
||
alternative: null
|
||
});
|
||
}
|
||
if (aborts(self.body)) {
|
||
if (self.alternative) {
|
||
var alt = self.alternative;
|
||
self.alternative = null;
|
||
return make_node(AST_BlockStatement, self, {
|
||
body: [ self, alt ]
|
||
}).optimize(compressor);
|
||
}
|
||
}
|
||
if (aborts(self.alternative)) {
|
||
var body = self.body;
|
||
self.body = self.alternative;
|
||
self.condition = negated_is_best ? negated : self.condition.negate(compressor);
|
||
self.alternative = null;
|
||
return make_node(AST_BlockStatement, self, {
|
||
body: [ self, body ]
|
||
}).optimize(compressor);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Switch, function(self, compressor) {
|
||
if (!compressor.option("switches")) return self;
|
||
var branch;
|
||
var value = self.expression.evaluate(compressor);
|
||
if (!(value instanceof AST_Node)) {
|
||
var orig = self.expression;
|
||
self.expression = make_node_from_constant(value, orig);
|
||
self.expression = best_of_expression(self.expression.transform(compressor), orig);
|
||
}
|
||
if (!compressor.option("dead_code")) return self;
|
||
if (value instanceof AST_Node) {
|
||
value = self.expression.tail_node().evaluate(compressor);
|
||
}
|
||
var decl = [];
|
||
var body = [];
|
||
var default_branch;
|
||
var exact_match;
|
||
// - compress self.body into `body`
|
||
// - find and deduplicate default branch
|
||
// - find the exact match (`case 1234` inside `switch(1234)`)
|
||
for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
|
||
branch = self.body[i];
|
||
if (branch instanceof AST_Default) {
|
||
if (!default_branch) {
|
||
default_branch = branch;
|
||
} else {
|
||
eliminate_branch(branch, body[body.length - 1]);
|
||
}
|
||
} else if (!(value instanceof AST_Node)) {
|
||
var exp = branch.expression.evaluate(compressor);
|
||
if (!(exp instanceof AST_Node) && exp !== value) {
|
||
eliminate_branch(branch, body[body.length - 1]);
|
||
continue;
|
||
}
|
||
if (exp instanceof AST_Node && !exp.has_side_effects(compressor)) {
|
||
exp = branch.expression.tail_node().evaluate(compressor);
|
||
}
|
||
if (exp === value) {
|
||
exact_match = branch;
|
||
if (default_branch) {
|
||
var default_index = body.indexOf(default_branch);
|
||
body.splice(default_index, 1);
|
||
eliminate_branch(default_branch, body[default_index - 1]);
|
||
default_branch = null;
|
||
}
|
||
}
|
||
}
|
||
body.push(branch);
|
||
}
|
||
// i < len if we found an exact_match. eliminate the rest
|
||
while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
|
||
self.body = body;
|
||
|
||
let default_or_exact = default_branch || exact_match;
|
||
default_branch = null;
|
||
exact_match = null;
|
||
|
||
// group equivalent branches so they will be located next to each other,
|
||
// that way the next micro-optimization will merge them.
|
||
// ** bail micro-optimization if not a simple switch case with breaks
|
||
if (body.every((branch, i) =>
|
||
(branch === default_or_exact || branch.expression instanceof AST_Constant)
|
||
&& (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
|
||
) {
|
||
for (let i = 0; i < body.length; i++) {
|
||
const branch = body[i];
|
||
for (let j = i + 1; j < body.length; j++) {
|
||
const next = body[j];
|
||
if (next.body.length === 0) continue;
|
||
const last_branch = j === (body.length - 1);
|
||
const equivalentBranch = branches_equivalent(next, branch, false);
|
||
if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
|
||
if (!equivalentBranch && last_branch) {
|
||
next.body.push(make_node(AST_Break));
|
||
}
|
||
|
||
// let's find previous siblings with inert fallthrough...
|
||
let x = j - 1;
|
||
let fallthroughDepth = 0;
|
||
while (x > i) {
|
||
if (is_inert_body(body[x--])) {
|
||
fallthroughDepth++;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
|
||
const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
|
||
body.splice(i + 1, 0, ...plucked);
|
||
i += plucked.length;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// merge equivalent branches in a row
|
||
for (let i = 0; i < body.length; i++) {
|
||
let branch = body[i];
|
||
if (branch.body.length === 0) continue;
|
||
if (!aborts(branch)) continue;
|
||
|
||
for (let j = i + 1; j < body.length; i++, j++) {
|
||
let next = body[j];
|
||
if (next.body.length === 0) continue;
|
||
if (
|
||
branches_equivalent(next, branch, false)
|
||
|| (j === body.length - 1 && branches_equivalent(next, branch, true))
|
||
) {
|
||
branch.body = [];
|
||
branch = next;
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Prune any empty branches at the end of the switch statement.
|
||
{
|
||
let i = body.length - 1;
|
||
for (; i >= 0; i--) {
|
||
let bbody = body[i].body;
|
||
while (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
|
||
if (!is_inert_body(body[i])) break;
|
||
}
|
||
// i now points to the index of a branch that contains a body. By incrementing, it's
|
||
// pointing to the first branch that's empty.
|
||
i++;
|
||
if (!default_or_exact || body.indexOf(default_or_exact) >= i) {
|
||
// The default behavior is to do nothing. We can take advantage of that to
|
||
// remove all case expressions that are side-effect free that also do
|
||
// nothing, since they'll default to doing nothing. But we can't remove any
|
||
// case expressions before one that would side-effect, since they may cause
|
||
// the side-effect to be skipped.
|
||
for (let j = body.length - 1; j >= i; j--) {
|
||
let branch = body[j];
|
||
if (branch === default_or_exact) {
|
||
default_or_exact = null;
|
||
eliminate_branch(body.pop());
|
||
} else if (!branch.expression.has_side_effects(compressor)) {
|
||
eliminate_branch(body.pop());
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// Prune side-effect free branches that fall into default.
|
||
DEFAULT: if (default_or_exact) {
|
||
let default_index = body.indexOf(default_or_exact);
|
||
let default_body_index = default_index;
|
||
for (; default_body_index < body.length - 1; default_body_index++) {
|
||
if (!is_inert_body(body[default_body_index])) break;
|
||
}
|
||
if (default_body_index < body.length - 1) {
|
||
break DEFAULT;
|
||
}
|
||
|
||
let side_effect_index = body.length - 1;
|
||
for (; side_effect_index >= 0; side_effect_index--) {
|
||
let branch = body[side_effect_index];
|
||
if (branch === default_or_exact) continue;
|
||
if (branch.expression.has_side_effects(compressor)) break;
|
||
}
|
||
// If the default behavior comes after any side-effect case expressions,
|
||
// then we can fold all side-effect free cases into the default branch.
|
||
// If the side-effect case is after the default, then any side-effect
|
||
// free cases could prevent the side-effect from occurring.
|
||
if (default_body_index > side_effect_index) {
|
||
let prev_body_index = default_index - 1;
|
||
for (; prev_body_index >= 0; prev_body_index--) {
|
||
if (!is_inert_body(body[prev_body_index])) break;
|
||
}
|
||
let before = Math.max(side_effect_index, prev_body_index) + 1;
|
||
let after = default_index;
|
||
if (side_effect_index > default_index) {
|
||
// If the default falls into the same body as a side-effect
|
||
// case, then we need preserve that case and only prune the
|
||
// cases after it.
|
||
after = side_effect_index;
|
||
body[side_effect_index].body = body[default_body_index].body;
|
||
} else {
|
||
// The default will be the last branch.
|
||
default_or_exact.body = body[default_body_index].body;
|
||
}
|
||
|
||
// Prune everything after the default (or last side-effect case)
|
||
// until the next case with a body.
|
||
body.splice(after + 1, default_body_index - after);
|
||
// Prune everything before the default that falls into it.
|
||
body.splice(before, default_index - before);
|
||
}
|
||
}
|
||
|
||
// See if we can remove the switch entirely if all cases (the default) fall into the same case body.
|
||
DEFAULT: if (default_or_exact) {
|
||
let i = body.findIndex(branch => !is_inert_body(branch));
|
||
let caseBody;
|
||
// `i` is equal to one of the following:
|
||
// - `-1`, there is no body in the switch statement.
|
||
// - `body.length - 1`, all cases fall into the same body.
|
||
// - anything else, there are multiple bodies in the switch.
|
||
if (i === body.length - 1) {
|
||
// All cases fall into the case body.
|
||
let branch = body[i];
|
||
if (has_nested_break(self)) break DEFAULT;
|
||
|
||
// This is the last case body, and we've already pruned any breaks, so it's
|
||
// safe to hoist.
|
||
caseBody = make_node(AST_BlockStatement, branch, {
|
||
body: branch.body
|
||
});
|
||
branch.body = [];
|
||
} else if (i !== -1) {
|
||
// If there are multiple bodies, then we cannot optimize anything.
|
||
break DEFAULT;
|
||
}
|
||
|
||
let sideEffect = body.find(
|
||
branch => branch !== default_or_exact && branch.expression.has_side_effects(compressor)
|
||
);
|
||
// If no cases cause a side-effect, we can eliminate the switch entirely.
|
||
if (!sideEffect) {
|
||
return make_node(AST_BlockStatement, self, {
|
||
body: decl.concat(
|
||
statement(self.expression),
|
||
default_or_exact.expression ? statement(default_or_exact.expression) : [],
|
||
caseBody || []
|
||
)
|
||
}).optimize(compressor);
|
||
}
|
||
|
||
// If we're this far, either there was no body or all cases fell into the same body.
|
||
// If there was no body, then we don't need a default branch (because the default is
|
||
// do nothing). If there was a body, we'll extract it to after the switch, so the
|
||
// switch's new default is to do nothing and we can still prune it.
|
||
const default_index = body.indexOf(default_or_exact);
|
||
body.splice(default_index, 1);
|
||
default_or_exact = null;
|
||
|
||
if (caseBody) {
|
||
// Recurse into switch statement one more time so that we can append the case body
|
||
// outside of the switch. This recursion will only happen once since we've pruned
|
||
// the default case.
|
||
return make_node(AST_BlockStatement, self, {
|
||
body: decl.concat(self, caseBody)
|
||
}).optimize(compressor);
|
||
}
|
||
// If we fall here, there is a default branch somewhere, there are no case bodies,
|
||
// and there's a side-effect somewhere. Just let the below paths take care of it.
|
||
}
|
||
|
||
// Reintegrate `decl` (var statements)
|
||
if (body.length > 0) {
|
||
body[0].body = decl.concat(body[0].body);
|
||
}
|
||
if (body.length == 0) {
|
||
return make_node(AST_BlockStatement, self, {
|
||
body: decl.concat(statement(self.expression))
|
||
}).optimize(compressor);
|
||
}
|
||
|
||
if (body.length == 1 && !has_nested_break(self)) {
|
||
// This is the last case body, and we've already pruned any breaks, so it's
|
||
// safe to hoist.
|
||
let branch = body[0];
|
||
return make_node(AST_If, self, {
|
||
condition: make_node(AST_Binary, self, {
|
||
operator: "===",
|
||
left: self.expression,
|
||
right: branch.expression,
|
||
}),
|
||
body: make_node(AST_BlockStatement, branch, {
|
||
body: branch.body
|
||
}),
|
||
alternative: null
|
||
}).optimize(compressor);
|
||
}
|
||
if (body.length === 2 && default_or_exact && !has_nested_break(self)) {
|
||
let branch = body[0] === default_or_exact ? body[1] : body[0];
|
||
let exact_exp = default_or_exact.expression && statement(default_or_exact.expression);
|
||
if (aborts(body[0])) {
|
||
// Only the first branch body could have a break (at the last statement)
|
||
let first = body[0];
|
||
if (is_break(first.body[first.body.length - 1], compressor)) {
|
||
first.body.pop();
|
||
}
|
||
return make_node(AST_If, self, {
|
||
condition: make_node(AST_Binary, self, {
|
||
operator: "===",
|
||
left: self.expression,
|
||
right: branch.expression,
|
||
}),
|
||
body: make_node(AST_BlockStatement, branch, {
|
||
body: branch.body
|
||
}),
|
||
alternative: make_node(AST_BlockStatement, default_or_exact, {
|
||
body: [].concat(
|
||
exact_exp || [],
|
||
default_or_exact.body
|
||
)
|
||
})
|
||
}).optimize(compressor);
|
||
}
|
||
let operator = "===";
|
||
let consequent = make_node(AST_BlockStatement, branch, {
|
||
body: branch.body,
|
||
});
|
||
let always = make_node(AST_BlockStatement, default_or_exact, {
|
||
body: [].concat(
|
||
exact_exp || [],
|
||
default_or_exact.body
|
||
)
|
||
});
|
||
if (body[0] === default_or_exact) {
|
||
operator = "!==";
|
||
let tmp = always;
|
||
always = consequent;
|
||
consequent = tmp;
|
||
}
|
||
return make_node(AST_BlockStatement, self, {
|
||
body: [
|
||
make_node(AST_If, self, {
|
||
condition: make_node(AST_Binary, self, {
|
||
operator: operator,
|
||
left: self.expression,
|
||
right: branch.expression,
|
||
}),
|
||
body: consequent,
|
||
alternative: null,
|
||
}),
|
||
always,
|
||
],
|
||
}).optimize(compressor);
|
||
}
|
||
return self;
|
||
|
||
function eliminate_branch(branch, prev) {
|
||
if (prev && !aborts(prev)) {
|
||
prev.body = prev.body.concat(branch.body);
|
||
} else {
|
||
extract_from_unreachable_code(compressor, branch, decl);
|
||
}
|
||
}
|
||
function branches_equivalent(branch, prev, insertBreak) {
|
||
let bbody = branch.body;
|
||
let pbody = prev.body;
|
||
if (insertBreak) {
|
||
bbody = bbody.concat(make_node(AST_Break));
|
||
}
|
||
if (bbody.length !== pbody.length) return false;
|
||
let bblock = make_node(AST_BlockStatement, branch, { body: bbody });
|
||
let pblock = make_node(AST_BlockStatement, prev, { body: pbody });
|
||
return bblock.equivalent_to(pblock);
|
||
}
|
||
function statement(body) {
|
||
return make_node(AST_SimpleStatement, body, { body });
|
||
}
|
||
function has_nested_break(root) {
|
||
let has_break = false;
|
||
|
||
let tw = new TreeWalker(node => {
|
||
if (has_break) return true;
|
||
if (node instanceof AST_Lambda) return true;
|
||
if (node instanceof AST_SimpleStatement) return true;
|
||
if (!is_break(node, tw)) return;
|
||
let parent = tw.parent();
|
||
if (
|
||
parent instanceof AST_SwitchBranch
|
||
&& parent.body[parent.body.length - 1] === node
|
||
) {
|
||
return;
|
||
}
|
||
has_break = true;
|
||
});
|
||
root.walk(tw);
|
||
return has_break;
|
||
}
|
||
function is_break(node, stack) {
|
||
return node instanceof AST_Break
|
||
&& stack.loopcontrol_target(node) === self;
|
||
}
|
||
function is_inert_body(branch) {
|
||
return !aborts(branch) && !make_node(AST_BlockStatement, branch, {
|
||
body: branch.body
|
||
}).has_side_effects(compressor);
|
||
}
|
||
});
|
||
|
||
def_optimize(AST_Try, function(self, compressor) {
|
||
if (self.bcatch && self.bfinally && self.bfinally.body.every(is_empty)) self.bfinally = null;
|
||
|
||
if (compressor.option("dead_code") && self.body.body.every(is_empty)) {
|
||
var body = [];
|
||
if (self.bcatch) {
|
||
extract_from_unreachable_code(compressor, self.bcatch, body);
|
||
}
|
||
if (self.bfinally) body.push(...self.bfinally.body);
|
||
return make_node(AST_BlockStatement, self, {
|
||
body: body
|
||
}).optimize(compressor);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
AST_Definitions.DEFMETHOD("to_assignments", function(compressor) {
|
||
var reduce_vars = compressor.option("reduce_vars");
|
||
var assignments = [];
|
||
|
||
for (const def of this.definitions) {
|
||
if (def.value) {
|
||
var name = make_node(AST_SymbolRef, def.name, def.name);
|
||
assignments.push(make_node(AST_Assign, def, {
|
||
operator : "=",
|
||
logical: false,
|
||
left : name,
|
||
right : def.value
|
||
}));
|
||
if (reduce_vars) name.definition().fixed = false;
|
||
}
|
||
const thedef = def.name.definition();
|
||
thedef.eliminated++;
|
||
thedef.replaced--;
|
||
}
|
||
|
||
if (assignments.length == 0) return null;
|
||
return make_sequence(this, assignments);
|
||
});
|
||
|
||
def_optimize(AST_Definitions, function(self) {
|
||
if (self.definitions.length == 0) {
|
||
return make_node(AST_EmptyStatement, self);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_VarDef, function(self, compressor) {
|
||
if (
|
||
self.name instanceof AST_SymbolLet
|
||
&& self.value != null
|
||
&& is_undefined(self.value, compressor)
|
||
) {
|
||
self.value = null;
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Import, function(self) {
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Call, function(self, compressor) {
|
||
var exp = self.expression;
|
||
var fn = exp;
|
||
inline_array_like_spread(self.args);
|
||
var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion));
|
||
|
||
if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
|
||
fn = fn.fixed_value();
|
||
}
|
||
|
||
var is_func = fn instanceof AST_Lambda;
|
||
|
||
if (is_func && fn.pinned()) return self;
|
||
|
||
if (compressor.option("unused")
|
||
&& simple_args
|
||
&& is_func
|
||
&& !fn.uses_arguments) {
|
||
var pos = 0, last = 0;
|
||
for (var i = 0, len = self.args.length; i < len; i++) {
|
||
if (fn.argnames[i] instanceof AST_Expansion) {
|
||
if (has_flag(fn.argnames[i].expression, UNUSED)) while (i < len) {
|
||
var node = self.args[i++].drop_side_effect_free(compressor);
|
||
if (node) {
|
||
self.args[pos++] = node;
|
||
}
|
||
} else while (i < len) {
|
||
self.args[pos++] = self.args[i++];
|
||
}
|
||
last = pos;
|
||
break;
|
||
}
|
||
var trim = i >= fn.argnames.length;
|
||
if (trim || has_flag(fn.argnames[i], UNUSED)) {
|
||
var node = self.args[i].drop_side_effect_free(compressor);
|
||
if (node) {
|
||
self.args[pos++] = node;
|
||
} else if (!trim) {
|
||
self.args[pos++] = make_node(AST_Number, self.args[i], {
|
||
value: 0
|
||
});
|
||
continue;
|
||
}
|
||
} else {
|
||
self.args[pos++] = self.args[i];
|
||
}
|
||
last = pos;
|
||
}
|
||
self.args.length = last;
|
||
}
|
||
|
||
if (
|
||
exp instanceof AST_Dot
|
||
&& exp.expression instanceof AST_SymbolRef
|
||
&& exp.expression.name === "console"
|
||
&& exp.expression.definition().undeclared
|
||
&& exp.property === "assert"
|
||
) {
|
||
const condition = self.args[0];
|
||
if (condition) {
|
||
const value = condition.evaluate(compressor);
|
||
|
||
if (value === 1 || value === true) {
|
||
return make_void_0(self).optimize(compressor);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (compressor.option("unsafe") && !exp.contains_optional()) {
|
||
if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) {
|
||
const [argument] = self.args;
|
||
if (argument instanceof AST_Array) {
|
||
return make_node(AST_Array, argument, {
|
||
elements: argument.elements
|
||
}).optimize(compressor);
|
||
}
|
||
}
|
||
if (is_undeclared_ref(exp)) switch (exp.name) {
|
||
case "Array":
|
||
if (self.args.length != 1) {
|
||
return make_node(AST_Array, self, {
|
||
elements: self.args
|
||
}).optimize(compressor);
|
||
} else if (self.args[0] instanceof AST_Number && self.args[0].value <= 11) {
|
||
const elements = [];
|
||
for (let i = 0; i < self.args[0].value; i++) elements.push(new AST_Hole);
|
||
return new AST_Array({ elements });
|
||
}
|
||
break;
|
||
case "Object":
|
||
if (self.args.length == 0) {
|
||
return make_node(AST_Object, self, {
|
||
properties: []
|
||
});
|
||
}
|
||
break;
|
||
case "String":
|
||
if (self.args.length == 0) return make_node(AST_String, self, {
|
||
value: ""
|
||
});
|
||
if (self.args.length <= 1) return make_node(AST_Binary, self, {
|
||
left: self.args[0],
|
||
operator: "+",
|
||
right: make_node(AST_String, self, { value: "" })
|
||
}).optimize(compressor);
|
||
break;
|
||
case "Number":
|
||
if (self.args.length == 0) return make_node(AST_Number, self, {
|
||
value: 0
|
||
});
|
||
if (self.args.length == 1 && compressor.option("unsafe_math")) {
|
||
return make_node(AST_UnaryPrefix, self, {
|
||
expression: self.args[0],
|
||
operator: "+"
|
||
}).optimize(compressor);
|
||
}
|
||
break;
|
||
case "Symbol":
|
||
if (self.args.length == 1 && self.args[0] instanceof AST_String && compressor.option("unsafe_symbols"))
|
||
self.args.length = 0;
|
||
break;
|
||
case "Boolean":
|
||
if (self.args.length == 0) return make_node(AST_False, self);
|
||
if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
|
||
expression: make_node(AST_UnaryPrefix, self, {
|
||
expression: self.args[0],
|
||
operator: "!"
|
||
}),
|
||
operator: "!"
|
||
}).optimize(compressor);
|
||
break;
|
||
case "RegExp":
|
||
var params = [];
|
||
if (self.args.length >= 1
|
||
&& self.args.length <= 2
|
||
&& self.args.every((arg) => {
|
||
var value = arg.evaluate(compressor);
|
||
params.push(value);
|
||
return arg !== value;
|
||
})
|
||
&& regexp_is_safe(params[0])
|
||
) {
|
||
let [ source, flags ] = params;
|
||
source = regexp_source_fix(new RegExp(source).source);
|
||
const rx = make_node(AST_RegExp, self, {
|
||
value: { source, flags }
|
||
});
|
||
if (rx._eval(compressor) !== rx) {
|
||
return rx;
|
||
}
|
||
}
|
||
break;
|
||
} else if (exp instanceof AST_Dot) switch(exp.property) {
|
||
case "toString":
|
||
if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) {
|
||
return make_node(AST_Binary, self, {
|
||
left: make_node(AST_String, self, { value: "" }),
|
||
operator: "+",
|
||
right: exp.expression
|
||
}).optimize(compressor);
|
||
}
|
||
break;
|
||
case "join":
|
||
if (exp.expression instanceof AST_Array) EXIT: {
|
||
var separator;
|
||
if (self.args.length > 0) {
|
||
separator = self.args[0].evaluate(compressor);
|
||
if (separator === self.args[0]) break EXIT; // not a constant
|
||
}
|
||
var elements = [];
|
||
var consts = [];
|
||
for (var i = 0, len = exp.expression.elements.length; i < len; i++) {
|
||
var el = exp.expression.elements[i];
|
||
if (el instanceof AST_Expansion) break EXIT;
|
||
var value = el.evaluate(compressor);
|
||
if (value !== el) {
|
||
consts.push(value);
|
||
} else {
|
||
if (consts.length > 0) {
|
||
elements.push(make_node(AST_String, self, {
|
||
value: consts.join(separator)
|
||
}));
|
||
consts.length = 0;
|
||
}
|
||
elements.push(el);
|
||
}
|
||
}
|
||
if (consts.length > 0) {
|
||
elements.push(make_node(AST_String, self, {
|
||
value: consts.join(separator)
|
||
}));
|
||
}
|
||
if (elements.length == 0) return make_node(AST_String, self, { value: "" });
|
||
if (elements.length == 1) {
|
||
if (elements[0].is_string(compressor)) {
|
||
return elements[0];
|
||
}
|
||
return make_node(AST_Binary, elements[0], {
|
||
operator : "+",
|
||
left : make_node(AST_String, self, { value: "" }),
|
||
right : elements[0]
|
||
});
|
||
}
|
||
if (separator == "") {
|
||
var first;
|
||
if (elements[0].is_string(compressor)
|
||
|| elements[1].is_string(compressor)) {
|
||
first = elements.shift();
|
||
} else {
|
||
first = make_node(AST_String, self, { value: "" });
|
||
}
|
||
return elements.reduce(function(prev, el) {
|
||
return make_node(AST_Binary, el, {
|
||
operator : "+",
|
||
left : prev,
|
||
right : el
|
||
});
|
||
}, first).optimize(compressor);
|
||
}
|
||
// need this awkward cloning to not affect original element
|
||
// best_of will decide which one to get through.
|
||
var node = self.clone();
|
||
node.expression = node.expression.clone();
|
||
node.expression.expression = node.expression.expression.clone();
|
||
node.expression.expression.elements = elements;
|
||
return best_of(compressor, self, node);
|
||
}
|
||
break;
|
||
case "charAt":
|
||
if (exp.expression.is_string(compressor)) {
|
||
var arg = self.args[0];
|
||
var index = arg ? arg.evaluate(compressor) : 0;
|
||
if (index !== arg) {
|
||
return make_node(AST_Sub, exp, {
|
||
expression: exp.expression,
|
||
property: make_node_from_constant(index | 0, arg || exp)
|
||
}).optimize(compressor);
|
||
}
|
||
}
|
||
break;
|
||
case "apply":
|
||
if (self.args.length == 2 && self.args[1] instanceof AST_Array) {
|
||
var args = self.args[1].elements.slice();
|
||
args.unshift(self.args[0]);
|
||
return make_node(AST_Call, self, {
|
||
expression: make_node(AST_Dot, exp, {
|
||
expression: exp.expression,
|
||
optional: false,
|
||
property: "call"
|
||
}),
|
||
args: args
|
||
}).optimize(compressor);
|
||
}
|
||
break;
|
||
case "call":
|
||
var func = exp.expression;
|
||
if (func instanceof AST_SymbolRef) {
|
||
func = func.fixed_value();
|
||
}
|
||
if (func instanceof AST_Lambda && !func.contains_this()) {
|
||
return (self.args.length ? make_sequence(this, [
|
||
self.args[0],
|
||
make_node(AST_Call, self, {
|
||
expression: exp.expression,
|
||
args: self.args.slice(1)
|
||
})
|
||
]) : make_node(AST_Call, self, {
|
||
expression: exp.expression,
|
||
args: []
|
||
})).optimize(compressor);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (compressor.option("unsafe_Function")
|
||
&& is_undeclared_ref(exp)
|
||
&& exp.name == "Function") {
|
||
// new Function() => function(){}
|
||
if (self.args.length == 0) return make_empty_function(self).optimize(compressor);
|
||
if (self.args.every((x) => x instanceof AST_String)) {
|
||
// quite a corner-case, but we can handle it:
|
||
// https://github.com/mishoo/UglifyJS2/issues/203
|
||
// if the code argument is a constant, then we can minify it.
|
||
try {
|
||
var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
|
||
return arg.value;
|
||
}).join(",") + "){" + self.args[self.args.length - 1].value + "})";
|
||
var ast = parse(code);
|
||
var mangle = compressor.mangle_options();
|
||
ast.figure_out_scope(mangle);
|
||
var comp = new Compressor(compressor.options, {
|
||
mangle_options: compressor._mangle_options
|
||
});
|
||
ast = ast.transform(comp);
|
||
ast.figure_out_scope(mangle);
|
||
ast.compute_char_frequency(mangle);
|
||
ast.mangle_names(mangle);
|
||
var fun;
|
||
walk(ast, node => {
|
||
if (is_func_expr(node)) {
|
||
fun = node;
|
||
return walk_abort;
|
||
}
|
||
});
|
||
var code = OutputStream();
|
||
AST_BlockStatement.prototype._codegen.call(fun, fun, code);
|
||
self.args = [
|
||
make_node(AST_String, self, {
|
||
value: fun.argnames.map(function(arg) {
|
||
return arg.print_to_string();
|
||
}).join(",")
|
||
}),
|
||
make_node(AST_String, self.args[self.args.length - 1], {
|
||
value: code.get().replace(/^{|}$/g, "")
|
||
})
|
||
];
|
||
return self;
|
||
} catch (ex) {
|
||
if (!(ex instanceof JS_Parse_Error)) {
|
||
throw ex;
|
||
}
|
||
|
||
// Otherwise, it crashes at runtime. Or maybe it's nonstandard syntax.
|
||
}
|
||
}
|
||
}
|
||
|
||
return inline_into_call(self, compressor);
|
||
});
|
||
|
||
/** Does this node contain optional property access or optional call? */
|
||
AST_Node.DEFMETHOD("contains_optional", function() {
|
||
if (
|
||
this instanceof AST_PropAccess
|
||
|| this instanceof AST_Call
|
||
|| this instanceof AST_Chain
|
||
) {
|
||
if (this.optional) {
|
||
return true;
|
||
} else {
|
||
return this.expression.contains_optional();
|
||
}
|
||
} else {
|
||
return false;
|
||
}
|
||
});
|
||
|
||
def_optimize(AST_New, function(self, compressor) {
|
||
if (
|
||
compressor.option("unsafe") &&
|
||
is_undeclared_ref(self.expression) &&
|
||
["Object", "RegExp", "Function", "Error", "Array"].includes(self.expression.name)
|
||
) return make_node(AST_Call, self, self).transform(compressor);
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Sequence, function(self, compressor) {
|
||
if (!compressor.option("side_effects")) return self;
|
||
var expressions = [];
|
||
filter_for_side_effects();
|
||
var end = expressions.length - 1;
|
||
trim_right_for_undefined();
|
||
if (end == 0) {
|
||
self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
|
||
if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
|
||
return self;
|
||
}
|
||
self.expressions = expressions;
|
||
return self;
|
||
|
||
function filter_for_side_effects() {
|
||
var first = first_in_statement(compressor);
|
||
var last = self.expressions.length - 1;
|
||
self.expressions.forEach(function(expr, index) {
|
||
if (index < last) expr = expr.drop_side_effect_free(compressor, first);
|
||
if (expr) {
|
||
merge_sequence(expressions, expr);
|
||
first = false;
|
||
}
|
||
});
|
||
}
|
||
|
||
function trim_right_for_undefined() {
|
||
while (end > 0 && is_undefined(expressions[end], compressor)) end--;
|
||
if (end < expressions.length - 1) {
|
||
expressions[end] = make_node(AST_UnaryPrefix, self, {
|
||
operator : "void",
|
||
expression : expressions[end]
|
||
});
|
||
expressions.length = end + 1;
|
||
}
|
||
}
|
||
});
|
||
|
||
AST_Unary.DEFMETHOD("lift_sequences", function(compressor) {
|
||
if (compressor.option("sequences")) {
|
||
if (this.expression instanceof AST_Sequence) {
|
||
var x = this.expression.expressions.slice();
|
||
var e = this.clone();
|
||
e.expression = x.pop();
|
||
x.push(e);
|
||
return make_sequence(this, x).optimize(compressor);
|
||
}
|
||
}
|
||
return this;
|
||
});
|
||
|
||
def_optimize(AST_UnaryPostfix, function(self, compressor) {
|
||
return self.lift_sequences(compressor);
|
||
});
|
||
|
||
def_optimize(AST_UnaryPrefix, function(self, compressor) {
|
||
var e = self.expression;
|
||
if (
|
||
self.operator == "delete" &&
|
||
!(
|
||
e instanceof AST_SymbolRef ||
|
||
e instanceof AST_PropAccess ||
|
||
e instanceof AST_Chain ||
|
||
is_identifier_atom(e)
|
||
)
|
||
) {
|
||
return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor);
|
||
}
|
||
// Short-circuit common `void 0`
|
||
if (self.operator === "void" && e instanceof AST_Number && e.value === 0) {
|
||
return unsafe_undefined_ref(self, compressor) || self;
|
||
}
|
||
var seq = self.lift_sequences(compressor);
|
||
if (seq !== self) {
|
||
return seq;
|
||
}
|
||
if (compressor.option("side_effects") && self.operator == "void") {
|
||
e = e.drop_side_effect_free(compressor);
|
||
if (e) {
|
||
self.expression = e;
|
||
return self;
|
||
} else {
|
||
return make_void_0(self).optimize(compressor);
|
||
}
|
||
}
|
||
if (compressor.in_boolean_context()) {
|
||
switch (self.operator) {
|
||
case "!":
|
||
if (e instanceof AST_UnaryPrefix && e.operator == "!") {
|
||
// !!foo ==> foo, if we're in boolean context
|
||
return e.expression;
|
||
}
|
||
if (e instanceof AST_Binary) {
|
||
self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
|
||
}
|
||
break;
|
||
case "typeof":
|
||
// typeof always returns a non-empty string, thus it's
|
||
// always true in booleans
|
||
// And we don't need to check if it's undeclared, because in typeof, that's OK
|
||
return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
|
||
e,
|
||
make_node(AST_True, self)
|
||
])).optimize(compressor);
|
||
}
|
||
}
|
||
if (self.operator == "-" && e instanceof AST_Infinity) {
|
||
e = e.transform(compressor);
|
||
}
|
||
if (e instanceof AST_Binary
|
||
&& (self.operator == "+" || self.operator == "-")
|
||
&& (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
|
||
return make_node(AST_Binary, self, {
|
||
operator: e.operator,
|
||
left: make_node(AST_UnaryPrefix, e.left, {
|
||
operator: self.operator,
|
||
expression: e.left
|
||
}),
|
||
right: e.right
|
||
});
|
||
}
|
||
|
||
if (compressor.option("evaluate")) {
|
||
// ~~x => x (in 32-bit context)
|
||
// ~~{32 bit integer} => {32 bit integer}
|
||
if (
|
||
self.operator === "~"
|
||
&& self.expression instanceof AST_UnaryPrefix
|
||
&& self.expression.operator === "~"
|
||
&& (compressor.in_32_bit_context(false) || self.expression.expression.is_32_bit_integer(compressor))
|
||
) {
|
||
return self.expression.expression;
|
||
}
|
||
|
||
// ~(x ^ y) => x ^ ~y
|
||
if (
|
||
self.operator === "~"
|
||
&& e instanceof AST_Binary
|
||
&& e.operator === "^"
|
||
) {
|
||
if (e.left instanceof AST_UnaryPrefix && e.left.operator === "~") {
|
||
// ~(~x ^ y) => x ^ y
|
||
e.left = e.left.bitwise_negate(compressor, true);
|
||
} else {
|
||
e.right = e.right.bitwise_negate(compressor, true);
|
||
}
|
||
return e;
|
||
}
|
||
}
|
||
|
||
if (
|
||
self.operator != "-"
|
||
// avoid infinite recursion of numerals
|
||
|| !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)
|
||
) {
|
||
var ev = self.evaluate(compressor);
|
||
if (ev !== self) {
|
||
ev = make_node_from_constant(ev, self).optimize(compressor);
|
||
return best_of(compressor, ev, self);
|
||
}
|
||
}
|
||
return self;
|
||
});
|
||
|
||
AST_Binary.DEFMETHOD("lift_sequences", function(compressor) {
|
||
if (compressor.option("sequences")) {
|
||
if (this.left instanceof AST_Sequence) {
|
||
var x = this.left.expressions.slice();
|
||
var e = this.clone();
|
||
e.left = x.pop();
|
||
x.push(e);
|
||
return make_sequence(this, x).optimize(compressor);
|
||
}
|
||
if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) {
|
||
var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
|
||
var x = this.right.expressions;
|
||
var last = x.length - 1;
|
||
for (var i = 0; i < last; i++) {
|
||
if (!assign && x[i].has_side_effects(compressor)) break;
|
||
}
|
||
if (i == last) {
|
||
x = x.slice();
|
||
var e = this.clone();
|
||
e.right = x.pop();
|
||
x.push(e);
|
||
return make_sequence(this, x).optimize(compressor);
|
||
} else if (i > 0) {
|
||
var e = this.clone();
|
||
e.right = make_sequence(this.right, x.slice(i));
|
||
x = x.slice(0, i);
|
||
x.push(e);
|
||
return make_sequence(this, x).optimize(compressor);
|
||
}
|
||
}
|
||
}
|
||
return this;
|
||
});
|
||
|
||
var commutativeOperators = makePredicate("== === != !== * & | ^");
|
||
function is_object(node) {
|
||
return node instanceof AST_Array
|
||
|| node instanceof AST_Lambda
|
||
|| node instanceof AST_Object
|
||
|| node instanceof AST_Class;
|
||
}
|
||
|
||
def_optimize(AST_Binary, function(self, compressor) {
|
||
function reversible() {
|
||
return self.left.is_constant()
|
||
|| self.right.is_constant()
|
||
|| !self.left.has_side_effects(compressor)
|
||
&& !self.right.has_side_effects(compressor);
|
||
}
|
||
function reverse(op) {
|
||
if (reversible()) {
|
||
if (op) self.operator = op;
|
||
var tmp = self.left;
|
||
self.left = self.right;
|
||
self.right = tmp;
|
||
}
|
||
}
|
||
if (compressor.option("lhs_constants") && commutativeOperators.has(self.operator)) {
|
||
if (self.right.is_constant()
|
||
&& !self.left.is_constant()) {
|
||
// if right is a constant, whatever side effects the
|
||
// left side might have could not influence the
|
||
// result. hence, force switch.
|
||
|
||
if (!(self.left instanceof AST_Binary
|
||
&& PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
|
||
reverse();
|
||
}
|
||
}
|
||
}
|
||
self = self.lift_sequences(compressor);
|
||
if (compressor.option("comparisons")) switch (self.operator) {
|
||
case "===":
|
||
case "!==":
|
||
var is_strict_comparison = true;
|
||
if (
|
||
(self.left.is_string(compressor) && self.right.is_string(compressor)) ||
|
||
(self.left.is_number(compressor) && self.right.is_number(compressor)) ||
|
||
(self.left.is_bigint(compressor) && self.right.is_bigint(compressor)) ||
|
||
(self.left.is_boolean() && self.right.is_boolean()) ||
|
||
self.left.equivalent_to(self.right)
|
||
) {
|
||
self.operator = self.operator.substr(0, 2);
|
||
}
|
||
|
||
// XXX: intentionally falling down to the next case
|
||
case "==":
|
||
case "!=":
|
||
// void 0 == x => null == x
|
||
if (!is_strict_comparison && is_undefined(self.left, compressor)) {
|
||
self.left = make_node(AST_Null, self.left);
|
||
// x == void 0 => x == null
|
||
} else if (!is_strict_comparison && is_undefined(self.right, compressor)) {
|
||
self.right = make_node(AST_Null, self.right);
|
||
} else if (compressor.option("typeofs")
|
||
// "undefined" == typeof x => undefined === x
|
||
&& self.left instanceof AST_String
|
||
&& self.left.value == "undefined"
|
||
&& self.right instanceof AST_UnaryPrefix
|
||
&& self.right.operator == "typeof") {
|
||
var expr = self.right.expression;
|
||
if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
|
||
: !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
|
||
self.right = expr;
|
||
self.left = make_void_0(self.left).optimize(compressor);
|
||
if (self.operator.length == 2) self.operator += "=";
|
||
}
|
||
} else if (compressor.option("typeofs")
|
||
// typeof x === "undefined" => x === undefined
|
||
&& self.left instanceof AST_UnaryPrefix
|
||
&& self.left.operator == "typeof"
|
||
&& self.right instanceof AST_String
|
||
&& self.right.value == "undefined") {
|
||
var expr = self.left.expression;
|
||
if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
|
||
: !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
|
||
self.left = expr;
|
||
self.right = make_void_0(self.right).optimize(compressor);
|
||
if (self.operator.length == 2) self.operator += "=";
|
||
}
|
||
} else if (self.left instanceof AST_SymbolRef
|
||
// obj !== obj => false
|
||
&& self.right instanceof AST_SymbolRef
|
||
&& self.left.definition() === self.right.definition()
|
||
&& is_object(self.left.fixed_value())) {
|
||
return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
|
||
} else if (self.left.is_32_bit_integer(compressor) && self.right.is_32_bit_integer(compressor)) {
|
||
const not = node => make_node(AST_UnaryPrefix, node, {
|
||
operator: "!",
|
||
expression: node
|
||
});
|
||
const booleanify = (node, truthy) => {
|
||
if (truthy) {
|
||
return compressor.in_boolean_context()
|
||
? node
|
||
: not(not(node));
|
||
} else {
|
||
return not(node);
|
||
}
|
||
};
|
||
|
||
// The only falsy 32-bit integer is 0
|
||
if (self.left instanceof AST_Number && self.left.value === 0) {
|
||
return booleanify(self.right, self.operator[0] === "!");
|
||
}
|
||
if (self.right instanceof AST_Number && self.right.value === 0) {
|
||
return booleanify(self.left, self.operator[0] === "!");
|
||
}
|
||
|
||
// Mask all-bits check
|
||
// (x & 0xFF) != 0xFF => !(~x & 0xFF)
|
||
let and_op, x, mask;
|
||
if (
|
||
(and_op =
|
||
self.left instanceof AST_Binary ? self.left
|
||
: self.right instanceof AST_Binary ? self.right : null)
|
||
&& (mask = and_op === self.left ? self.right : self.left)
|
||
&& and_op.operator === "&"
|
||
&& mask instanceof AST_Number
|
||
&& mask.is_32_bit_integer(compressor)
|
||
&& (x =
|
||
and_op.left.equivalent_to(mask) ? and_op.right
|
||
: and_op.right.equivalent_to(mask) ? and_op.left : null)
|
||
) {
|
||
let optimized = booleanify(make_node(AST_Binary, self, {
|
||
operator: "&",
|
||
left: mask,
|
||
right: make_node(AST_UnaryPrefix, self, {
|
||
operator: "~",
|
||
expression: x
|
||
})
|
||
}), self.operator[0] === "!");
|
||
|
||
return best_of(compressor, optimized, self);
|
||
}
|
||
}
|
||
break;
|
||
case "&&":
|
||
case "||":
|
||
var lhs = self.left;
|
||
if (lhs.operator == self.operator) {
|
||
lhs = lhs.right;
|
||
}
|
||
if (lhs instanceof AST_Binary
|
||
&& lhs.operator == (self.operator == "&&" ? "!==" : "===")
|
||
&& self.right instanceof AST_Binary
|
||
&& lhs.operator == self.right.operator
|
||
&& (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null
|
||
|| lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor))
|
||
&& !lhs.right.has_side_effects(compressor)
|
||
&& lhs.right.equivalent_to(self.right.right)) {
|
||
var combined = make_node(AST_Binary, self, {
|
||
operator: lhs.operator.slice(0, -1),
|
||
left: make_node(AST_Null, self),
|
||
right: lhs.right
|
||
});
|
||
if (lhs !== self.left) {
|
||
combined = make_node(AST_Binary, self, {
|
||
operator: self.operator,
|
||
left: self.left.left,
|
||
right: combined
|
||
});
|
||
}
|
||
return combined;
|
||
}
|
||
break;
|
||
}
|
||
if (self.operator == "+" && compressor.in_boolean_context()) {
|
||
var ll = self.left.evaluate(compressor);
|
||
var rr = self.right.evaluate(compressor);
|
||
if (ll && typeof ll == "string") {
|
||
return make_sequence(self, [
|
||
self.right,
|
||
make_node(AST_True, self)
|
||
]).optimize(compressor);
|
||
}
|
||
if (rr && typeof rr == "string") {
|
||
return make_sequence(self, [
|
||
self.left,
|
||
make_node(AST_True, self)
|
||
]).optimize(compressor);
|
||
}
|
||
}
|
||
if (compressor.option("comparisons") && self.is_boolean()) {
|
||
if (!(compressor.parent() instanceof AST_Binary)
|
||
|| compressor.parent() instanceof AST_Assign) {
|
||
var negated = make_node(AST_UnaryPrefix, self, {
|
||
operator: "!",
|
||
expression: self.negate(compressor, first_in_statement(compressor))
|
||
});
|
||
self = best_of(compressor, self, negated);
|
||
}
|
||
if (compressor.option("unsafe_comps")) {
|
||
switch (self.operator) {
|
||
case "<": reverse(">"); break;
|
||
case "<=": reverse(">="); break;
|
||
}
|
||
}
|
||
}
|
||
if (self.operator == "+") {
|
||
if (self.right instanceof AST_String
|
||
&& self.right.getValue() == ""
|
||
&& self.left.is_string(compressor)) {
|
||
return self.left;
|
||
}
|
||
if (self.left instanceof AST_String
|
||
&& self.left.getValue() == ""
|
||
&& self.right.is_string(compressor)) {
|
||
return self.right;
|
||
}
|
||
if (self.left instanceof AST_Binary
|
||
&& self.left.operator == "+"
|
||
&& self.left.left instanceof AST_String
|
||
&& self.left.left.getValue() == ""
|
||
&& self.right.is_string(compressor)) {
|
||
self.left = self.left.right;
|
||
return self;
|
||
}
|
||
}
|
||
if (compressor.option("evaluate")) {
|
||
switch (self.operator) {
|
||
case "&&":
|
||
var ll = has_flag(self.left, TRUTHY)
|
||
? true
|
||
: has_flag(self.left, FALSY)
|
||
? false
|
||
: self.left.evaluate(compressor);
|
||
if (!ll) {
|
||
return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
|
||
} else if (!(ll instanceof AST_Node)) {
|
||
return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
|
||
}
|
||
var rr = self.right.evaluate(compressor);
|
||
if (!rr) {
|
||
if (compressor.in_boolean_context()) {
|
||
return make_sequence(self, [
|
||
self.left,
|
||
make_node(AST_False, self)
|
||
]).optimize(compressor);
|
||
} else {
|
||
set_flag(self, FALSY);
|
||
}
|
||
} else if (!(rr instanceof AST_Node)) {
|
||
var parent = compressor.parent();
|
||
if (parent.operator == "&&" && parent.left === compressor.self() || compressor.in_boolean_context()) {
|
||
return self.left.optimize(compressor);
|
||
}
|
||
}
|
||
// x || false && y ---> x ? y : false
|
||
if (self.left.operator == "||") {
|
||
var lr = self.left.right.evaluate(compressor);
|
||
if (!lr) return make_node(AST_Conditional, self, {
|
||
condition: self.left.left,
|
||
consequent: self.right,
|
||
alternative: self.left.right
|
||
}).optimize(compressor);
|
||
}
|
||
break;
|
||
case "||":
|
||
var ll = has_flag(self.left, TRUTHY)
|
||
? true
|
||
: has_flag(self.left, FALSY)
|
||
? false
|
||
: self.left.evaluate(compressor);
|
||
if (!ll) {
|
||
return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
|
||
} else if (!(ll instanceof AST_Node)) {
|
||
return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
|
||
}
|
||
var rr = self.right.evaluate(compressor);
|
||
if (!rr) {
|
||
var parent = compressor.parent();
|
||
if (parent.operator == "||" && parent.left === compressor.self() || compressor.in_boolean_context()) {
|
||
return self.left.optimize(compressor);
|
||
}
|
||
} else if (!(rr instanceof AST_Node)) {
|
||
if (compressor.in_boolean_context()) {
|
||
return make_sequence(self, [
|
||
self.left,
|
||
make_node(AST_True, self)
|
||
]).optimize(compressor);
|
||
} else {
|
||
set_flag(self, TRUTHY);
|
||
}
|
||
}
|
||
if (self.left.operator == "&&") {
|
||
var lr = self.left.right.evaluate(compressor);
|
||
if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
|
||
condition: self.left.left,
|
||
consequent: self.left.right,
|
||
alternative: self.right
|
||
}).optimize(compressor);
|
||
}
|
||
break;
|
||
case "??":
|
||
if (is_nullish(self.left, compressor)) {
|
||
return self.right;
|
||
}
|
||
|
||
var ll = self.left.evaluate(compressor);
|
||
if (!(ll instanceof AST_Node)) {
|
||
// if we know the value for sure we can simply compute right away.
|
||
return ll == null ? self.right : self.left;
|
||
}
|
||
|
||
if (compressor.in_boolean_context()) {
|
||
const rr = self.right.evaluate(compressor);
|
||
if (!(rr instanceof AST_Node) && !rr) {
|
||
return self.left;
|
||
}
|
||
}
|
||
}
|
||
var associative = true;
|
||
switch (self.operator) {
|
||
case "+":
|
||
// (x + "foo") + "bar" => x + "foobar"
|
||
if (self.right instanceof AST_Constant
|
||
&& self.left instanceof AST_Binary
|
||
&& self.left.operator == "+"
|
||
&& self.left.is_string(compressor)) {
|
||
var binary = make_node(AST_Binary, self, {
|
||
operator: "+",
|
||
left: self.left.right,
|
||
right: self.right,
|
||
});
|
||
var r = binary.optimize(compressor);
|
||
if (binary !== r) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: "+",
|
||
left: self.left.left,
|
||
right: r
|
||
});
|
||
}
|
||
}
|
||
// (x + "foo") + ("bar" + y) => (x + "foobar") + y
|
||
if (self.left instanceof AST_Binary
|
||
&& self.left.operator == "+"
|
||
&& self.left.is_string(compressor)
|
||
&& self.right instanceof AST_Binary
|
||
&& self.right.operator == "+"
|
||
&& self.right.is_string(compressor)) {
|
||
var binary = make_node(AST_Binary, self, {
|
||
operator: "+",
|
||
left: self.left.right,
|
||
right: self.right.left,
|
||
});
|
||
var m = binary.optimize(compressor);
|
||
if (binary !== m) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: "+",
|
||
left: make_node(AST_Binary, self.left, {
|
||
operator: "+",
|
||
left: self.left.left,
|
||
right: m
|
||
}),
|
||
right: self.right.right
|
||
});
|
||
}
|
||
}
|
||
// a + -b => a - b
|
||
if (self.right instanceof AST_UnaryPrefix
|
||
&& self.right.operator == "-"
|
||
&& self.left.is_number_or_bigint(compressor)) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: "-",
|
||
left: self.left,
|
||
right: self.right.expression
|
||
});
|
||
break;
|
||
}
|
||
// -a + b => b - a
|
||
if (self.left instanceof AST_UnaryPrefix
|
||
&& self.left.operator == "-"
|
||
&& reversible()
|
||
&& self.right.is_number_or_bigint(compressor)) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: "-",
|
||
left: self.right,
|
||
right: self.left.expression
|
||
});
|
||
break;
|
||
}
|
||
// `foo${bar}baz` + 1 => `foo${bar}baz1`
|
||
if (self.left instanceof AST_TemplateString) {
|
||
var l = self.left;
|
||
var r = self.right.evaluate(compressor);
|
||
if (r != self.right) {
|
||
l.segments[l.segments.length - 1].value += String(r);
|
||
return l;
|
||
}
|
||
}
|
||
// 1 + `foo${bar}baz` => `1foo${bar}baz`
|
||
if (self.right instanceof AST_TemplateString) {
|
||
var r = self.right;
|
||
var l = self.left.evaluate(compressor);
|
||
if (l != self.left) {
|
||
r.segments[0].value = String(l) + r.segments[0].value;
|
||
return r;
|
||
}
|
||
}
|
||
// `1${bar}2` + `foo${bar}baz` => `1${bar}2foo${bar}baz`
|
||
if (self.left instanceof AST_TemplateString
|
||
&& self.right instanceof AST_TemplateString) {
|
||
var l = self.left;
|
||
var segments = l.segments;
|
||
var r = self.right;
|
||
segments[segments.length - 1].value += r.segments[0].value;
|
||
for (var i = 1; i < r.segments.length; i++) {
|
||
segments.push(r.segments[i]);
|
||
}
|
||
return l;
|
||
}
|
||
case "*":
|
||
associative = compressor.option("unsafe_math");
|
||
case "&":
|
||
case "|":
|
||
case "^":
|
||
// a + +b => +b + a
|
||
if (
|
||
self.left.is_number_or_bigint(compressor)
|
||
&& self.right.is_number_or_bigint(compressor)
|
||
&& reversible()
|
||
&& !(self.left instanceof AST_Binary
|
||
&& self.left.operator != self.operator
|
||
&& PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
|
||
var reversed = make_node(AST_Binary, self, {
|
||
operator: self.operator,
|
||
left: self.right,
|
||
right: self.left
|
||
});
|
||
if (self.right instanceof AST_Constant
|
||
&& !(self.left instanceof AST_Constant)) {
|
||
self = best_of(compressor, reversed, self);
|
||
} else {
|
||
self = best_of(compressor, self, reversed);
|
||
}
|
||
}
|
||
if (associative && self.is_number_or_bigint(compressor)) {
|
||
// a + (b + c) => (a + b) + c
|
||
if (self.right instanceof AST_Binary
|
||
&& self.right.operator == self.operator) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: self.operator,
|
||
left: make_node(AST_Binary, self.left, {
|
||
operator: self.operator,
|
||
left: self.left,
|
||
right: self.right.left,
|
||
start: self.left.start,
|
||
end: self.right.left.end
|
||
}),
|
||
right: self.right.right
|
||
});
|
||
}
|
||
// (n + 2) + 3 => 5 + n
|
||
// (2 * n) * 3 => 6 + n
|
||
if (self.right instanceof AST_Constant
|
||
&& self.left instanceof AST_Binary
|
||
&& self.left.operator == self.operator) {
|
||
if (self.left.left instanceof AST_Constant) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: self.operator,
|
||
left: make_node(AST_Binary, self.left, {
|
||
operator: self.operator,
|
||
left: self.left.left,
|
||
right: self.right,
|
||
start: self.left.left.start,
|
||
end: self.right.end
|
||
}),
|
||
right: self.left.right
|
||
});
|
||
} else if (self.left.right instanceof AST_Constant) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: self.operator,
|
||
left: make_node(AST_Binary, self.left, {
|
||
operator: self.operator,
|
||
left: self.left.right,
|
||
right: self.right,
|
||
start: self.left.right.start,
|
||
end: self.right.end
|
||
}),
|
||
right: self.left.left
|
||
});
|
||
}
|
||
}
|
||
// (a | 1) | (2 | d) => (3 | a) | b
|
||
if (self.left instanceof AST_Binary
|
||
&& self.left.operator == self.operator
|
||
&& self.left.right instanceof AST_Constant
|
||
&& self.right instanceof AST_Binary
|
||
&& self.right.operator == self.operator
|
||
&& self.right.left instanceof AST_Constant) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: self.operator,
|
||
left: make_node(AST_Binary, self.left, {
|
||
operator: self.operator,
|
||
left: make_node(AST_Binary, self.left.left, {
|
||
operator: self.operator,
|
||
left: self.left.right,
|
||
right: self.right.left,
|
||
start: self.left.right.start,
|
||
end: self.right.left.end
|
||
}),
|
||
right: self.left.left
|
||
}),
|
||
right: self.right.right
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// bitwise ops
|
||
if (bitwise_binop.has(self.operator)) {
|
||
// Use De Morgan's laws
|
||
// z & (X | y)
|
||
// => z & X (given y & z === 0)
|
||
// => z & X | {y & z} (given y & z !== 0)
|
||
let y, z, x_node, y_node, z_node = self.left;
|
||
if (
|
||
self.operator === "&"
|
||
&& self.right instanceof AST_Binary
|
||
&& self.right.operator === "|"
|
||
&& typeof (z = self.left.evaluate(compressor)) === "number"
|
||
) {
|
||
if (typeof (y = self.right.right.evaluate(compressor)) === "number") {
|
||
// z & (X | y)
|
||
x_node = self.right.left;
|
||
y_node = self.right.right;
|
||
} else if (typeof (y = self.right.left.evaluate(compressor)) === "number") {
|
||
// z & (y | X)
|
||
x_node = self.right.right;
|
||
y_node = self.right.left;
|
||
}
|
||
|
||
if (x_node && y_node) {
|
||
if ((y & z) === 0) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: self.operator,
|
||
left: z_node,
|
||
right: x_node
|
||
});
|
||
} else {
|
||
const reordered_ops = make_node(AST_Binary, self, {
|
||
operator: "|",
|
||
left: make_node(AST_Binary, self, {
|
||
operator: "&",
|
||
left: x_node,
|
||
right: z_node
|
||
}),
|
||
right: make_node_from_constant(y & z, y_node),
|
||
});
|
||
|
||
self = best_of(compressor, self, reordered_ops);
|
||
}
|
||
}
|
||
}
|
||
|
||
// x | x => 0 | x
|
||
// x & x => 0 | x
|
||
if (
|
||
(self.operator === "|" || self.operator === "&")
|
||
&& self.left.equivalent_to(self.right)
|
||
&& !self.left.has_side_effects(compressor)
|
||
&& compressor.in_32_bit_context(true)
|
||
) {
|
||
self.left = make_node(AST_Number, self, { value: 0 });
|
||
self.operator = "|";
|
||
}
|
||
|
||
// ~x ^ ~y => x ^ y
|
||
if (
|
||
self.operator === "^"
|
||
&& self.left instanceof AST_UnaryPrefix
|
||
&& self.left.operator === "~"
|
||
&& self.right instanceof AST_UnaryPrefix
|
||
&& self.right.operator === "~"
|
||
) {
|
||
self = make_node(AST_Binary, self, {
|
||
operator: "^",
|
||
left: self.left.expression,
|
||
right: self.right.expression
|
||
});
|
||
}
|
||
|
||
|
||
// Shifts that do nothing
|
||
// {anything} >> 0 => {anything} | 0
|
||
// {anything} << 0 => {anything} | 0
|
||
if (
|
||
(self.operator === "<<" || self.operator === ">>")
|
||
&& self.right instanceof AST_Number && self.right.value === 0
|
||
) {
|
||
self.operator = "|";
|
||
}
|
||
|
||
// Find useless to-bitwise conversions
|
||
// {32 bit integer} | 0 => {32 bit integer}
|
||
// {32 bit integer} ^ 0 => {32 bit integer}
|
||
const zero_side = self.right instanceof AST_Number && self.right.value === 0 ? self.right
|
||
: self.left instanceof AST_Number && self.left.value === 0 ? self.left
|
||
: null;
|
||
const non_zero_side = zero_side && (zero_side === self.right ? self.left : self.right);
|
||
if (
|
||
zero_side
|
||
&& (self.operator === "|" || self.operator === "^")
|
||
&& (non_zero_side.is_32_bit_integer(compressor) || compressor.in_32_bit_context(true))
|
||
) {
|
||
return non_zero_side;
|
||
}
|
||
|
||
// {anything} & 0 => 0
|
||
if (
|
||
zero_side
|
||
&& self.operator === "&"
|
||
&& !non_zero_side.has_side_effects(compressor)
|
||
&& non_zero_side.is_32_bit_integer(compressor)
|
||
) {
|
||
return zero_side;
|
||
}
|
||
|
||
// ~0 is all ones, as well as -1.
|
||
// We can ellide some operations with it.
|
||
const is_full_mask = (node) =>
|
||
node instanceof AST_Number && node.value === -1
|
||
||
|
||
node instanceof AST_UnaryPrefix
|
||
&& node.operator === "-"
|
||
&& node.expression instanceof AST_Number
|
||
&& node.expression.value === 1;
|
||
|
||
const full_mask = is_full_mask(self.right) ? self.right
|
||
: is_full_mask(self.left) ? self.left
|
||
: null;
|
||
const other_side = (full_mask === self.right ? self.left : self.right);
|
||
|
||
// {32 bit integer} & -1 => {32 bit integer}
|
||
if (
|
||
full_mask
|
||
&& self.operator === "&"
|
||
&& (
|
||
other_side.is_32_bit_integer(compressor)
|
||
|| compressor.in_32_bit_context(true)
|
||
)
|
||
) {
|
||
return other_side;
|
||
}
|
||
|
||
// {anything} ^ -1 => ~{anything}
|
||
if (
|
||
full_mask
|
||
&& self.operator === "^"
|
||
&& (
|
||
other_side.is_32_bit_integer(compressor)
|
||
|| compressor.in_32_bit_context(true)
|
||
)
|
||
) {
|
||
return other_side.bitwise_negate(compressor);
|
||
}
|
||
}
|
||
}
|
||
// x && (y && z) ==> x && y && z
|
||
// x || (y || z) ==> x || y || z
|
||
// x + ("y" + z) ==> x + "y" + z
|
||
// "x" + (y + "z")==> "x" + y + "z"
|
||
if (self.right instanceof AST_Binary
|
||
&& self.right.operator == self.operator
|
||
&& (lazy_op.has(self.operator)
|
||
|| (self.operator == "+"
|
||
&& (self.right.left.is_string(compressor)
|
||
|| (self.left.is_string(compressor)
|
||
&& self.right.right.is_string(compressor)))))
|
||
) {
|
||
self.left = make_node(AST_Binary, self.left, {
|
||
operator : self.operator,
|
||
left : self.left.transform(compressor),
|
||
right : self.right.left.transform(compressor)
|
||
});
|
||
self.right = self.right.right.transform(compressor);
|
||
return self.transform(compressor);
|
||
}
|
||
var ev = self.evaluate(compressor);
|
||
if (ev !== self) {
|
||
ev = make_node_from_constant(ev, self).optimize(compressor);
|
||
return best_of(compressor, ev, self);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_SymbolExport, function(self) {
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_SymbolRef, function(self, compressor) {
|
||
if (
|
||
!compressor.option("ie8")
|
||
&& is_undeclared_ref(self)
|
||
&& !compressor.find_parent(AST_With)
|
||
) {
|
||
switch (self.name) {
|
||
case "undefined":
|
||
return make_node(AST_Undefined, self).optimize(compressor);
|
||
case "NaN":
|
||
return make_node(AST_NaN, self).optimize(compressor);
|
||
case "Infinity":
|
||
return make_node(AST_Infinity, self).optimize(compressor);
|
||
}
|
||
}
|
||
|
||
if (compressor.option("reduce_vars") && !compressor.is_lhs()) {
|
||
return inline_into_symbolref(self, compressor);
|
||
} else {
|
||
return self;
|
||
}
|
||
});
|
||
|
||
function is_atomic(lhs, self) {
|
||
return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
|
||
}
|
||
|
||
/** Apply the `unsafe_undefined` option: find a variable called `undefined` and turn `self` into a reference to it. */
|
||
function unsafe_undefined_ref(self, compressor) {
|
||
if (compressor.option("unsafe_undefined")) {
|
||
var undef = find_variable(compressor, "undefined");
|
||
if (undef) {
|
||
var ref = make_node(AST_SymbolRef, self, {
|
||
name : "undefined",
|
||
scope : undef.scope,
|
||
thedef : undef
|
||
});
|
||
set_flag(ref, UNDEFINED);
|
||
return ref;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
def_optimize(AST_Undefined, function(self, compressor) {
|
||
var symbolref = unsafe_undefined_ref(self, compressor);
|
||
if (symbolref) return symbolref;
|
||
var lhs = compressor.is_lhs();
|
||
if (lhs && is_atomic(lhs, self)) return self;
|
||
return make_void_0(self);
|
||
});
|
||
|
||
def_optimize(AST_Infinity, function(self, compressor) {
|
||
var lhs = compressor.is_lhs();
|
||
if (lhs && is_atomic(lhs, self)) return self;
|
||
if (
|
||
compressor.option("keep_infinity")
|
||
&& !(lhs && !is_atomic(lhs, self))
|
||
&& !find_variable(compressor, "Infinity")
|
||
) {
|
||
return self;
|
||
}
|
||
return make_node(AST_Binary, self, {
|
||
operator: "/",
|
||
left: make_node(AST_Number, self, {
|
||
value: 1
|
||
}),
|
||
right: make_node(AST_Number, self, {
|
||
value: 0
|
||
})
|
||
});
|
||
});
|
||
|
||
def_optimize(AST_NaN, function(self, compressor) {
|
||
var lhs = compressor.is_lhs();
|
||
if (lhs && !is_atomic(lhs, self)
|
||
|| find_variable(compressor, "NaN")) {
|
||
return make_node(AST_Binary, self, {
|
||
operator: "/",
|
||
left: make_node(AST_Number, self, {
|
||
value: 0
|
||
}),
|
||
right: make_node(AST_Number, self, {
|
||
value: 0
|
||
})
|
||
});
|
||
}
|
||
return self;
|
||
});
|
||
|
||
const ASSIGN_OPS = makePredicate("+ - / * % >> << >>> | ^ &");
|
||
const ASSIGN_OPS_COMMUTATIVE = makePredicate("* | ^ &");
|
||
def_optimize(AST_Assign, function(self, compressor) {
|
||
if (self.logical) {
|
||
return self.lift_sequences(compressor);
|
||
}
|
||
|
||
var def;
|
||
// x = x ---> x
|
||
if (
|
||
self.operator === "="
|
||
&& self.left instanceof AST_SymbolRef
|
||
&& self.left.name !== "arguments"
|
||
&& !(def = self.left.definition()).undeclared
|
||
&& self.right.equivalent_to(self.left)
|
||
) {
|
||
return self.right;
|
||
}
|
||
|
||
if (compressor.option("dead_code")
|
||
&& self.left instanceof AST_SymbolRef
|
||
&& (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) {
|
||
var level = 0, node, parent = self;
|
||
do {
|
||
node = parent;
|
||
parent = compressor.parent(level++);
|
||
if (parent instanceof AST_Exit) {
|
||
if (in_try(level, parent)) break;
|
||
if (is_reachable(def.scope, [ def ])) break;
|
||
if (self.operator == "=") return self.right;
|
||
def.fixed = false;
|
||
return make_node(AST_Binary, self, {
|
||
operator: self.operator.slice(0, -1),
|
||
left: self.left,
|
||
right: self.right
|
||
}).optimize(compressor);
|
||
}
|
||
} while (parent instanceof AST_Binary && parent.right === node
|
||
|| parent instanceof AST_Sequence && parent.tail_node() === node);
|
||
}
|
||
self = self.lift_sequences(compressor);
|
||
|
||
if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) {
|
||
// x = expr1 OP expr2
|
||
if (self.right.left instanceof AST_SymbolRef
|
||
&& self.right.left.name == self.left.name
|
||
&& ASSIGN_OPS.has(self.right.operator)) {
|
||
// x = x - 2 ---> x -= 2
|
||
self.operator = self.right.operator + "=";
|
||
self.right = self.right.right;
|
||
} else if (self.right.right instanceof AST_SymbolRef
|
||
&& self.right.right.name == self.left.name
|
||
&& ASSIGN_OPS_COMMUTATIVE.has(self.right.operator)
|
||
&& !self.right.left.has_side_effects(compressor)) {
|
||
// x = 2 & x ---> x &= 2
|
||
self.operator = self.right.operator + "=";
|
||
self.right = self.right.left;
|
||
}
|
||
}
|
||
return self;
|
||
|
||
function in_try(level, node) {
|
||
function may_assignment_throw() {
|
||
const right = self.right;
|
||
self.right = make_node(AST_Null, right);
|
||
const may_throw = node.may_throw(compressor);
|
||
self.right = right;
|
||
|
||
return may_throw;
|
||
}
|
||
|
||
var stop_at = self.left.definition().scope.get_defun_scope();
|
||
var parent;
|
||
while ((parent = compressor.parent(level++)) !== stop_at) {
|
||
if (parent instanceof AST_Try) {
|
||
if (parent.bfinally) return true;
|
||
if (parent.bcatch && may_assignment_throw()) return true;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
def_optimize(AST_DefaultAssign, function(self, compressor) {
|
||
if (!compressor.option("evaluate")) {
|
||
return self;
|
||
}
|
||
var evaluateRight = self.right.evaluate(compressor);
|
||
|
||
// `[x = undefined] = foo` ---> `[x] = foo`
|
||
// `(arg = undefined) => ...` ---> `(arg) => ...` (unless `keep_fargs`)
|
||
// `((arg = undefined) => ...)()` ---> `((arg) => ...)()`
|
||
let lambda, iife;
|
||
if (evaluateRight === undefined) {
|
||
if (
|
||
(lambda = compressor.parent()) instanceof AST_Lambda
|
||
? (
|
||
compressor.option("keep_fargs") === false
|
||
|| (iife = compressor.parent(1)).TYPE === "Call"
|
||
&& iife.expression === lambda
|
||
)
|
||
: true
|
||
) {
|
||
self = self.left;
|
||
}
|
||
} else if (evaluateRight !== self.right) {
|
||
evaluateRight = make_node_from_constant(evaluateRight, self.right);
|
||
self.right = best_of_expression(evaluateRight, self.right);
|
||
}
|
||
|
||
return self;
|
||
});
|
||
|
||
function is_nullish_check(check, check_subject, compressor) {
|
||
if (check_subject.may_throw(compressor)) return false;
|
||
|
||
let nullish_side;
|
||
|
||
// foo == null
|
||
if (
|
||
check instanceof AST_Binary
|
||
&& check.operator === "=="
|
||
// which side is nullish?
|
||
&& (
|
||
(nullish_side = is_nullish(check.left, compressor) && check.left)
|
||
|| (nullish_side = is_nullish(check.right, compressor) && check.right)
|
||
)
|
||
// is the other side the same as the check_subject
|
||
&& (
|
||
nullish_side === check.left
|
||
? check.right
|
||
: check.left
|
||
).equivalent_to(check_subject)
|
||
) {
|
||
return true;
|
||
}
|
||
|
||
// foo === null || foo === undefined
|
||
if (check instanceof AST_Binary && check.operator === "||") {
|
||
let null_cmp;
|
||
let undefined_cmp;
|
||
|
||
const find_comparison = cmp => {
|
||
if (!(
|
||
cmp instanceof AST_Binary
|
||
&& (cmp.operator === "===" || cmp.operator === "==")
|
||
)) {
|
||
return false;
|
||
}
|
||
|
||
let found = 0;
|
||
let defined_side;
|
||
|
||
if (cmp.left instanceof AST_Null) {
|
||
found++;
|
||
null_cmp = cmp;
|
||
defined_side = cmp.right;
|
||
}
|
||
if (cmp.right instanceof AST_Null) {
|
||
found++;
|
||
null_cmp = cmp;
|
||
defined_side = cmp.left;
|
||
}
|
||
if (is_undefined(cmp.left, compressor)) {
|
||
found++;
|
||
undefined_cmp = cmp;
|
||
defined_side = cmp.right;
|
||
}
|
||
if (is_undefined(cmp.right, compressor)) {
|
||
found++;
|
||
undefined_cmp = cmp;
|
||
defined_side = cmp.left;
|
||
}
|
||
|
||
if (found !== 1) {
|
||
return false;
|
||
}
|
||
|
||
if (!defined_side.equivalent_to(check_subject)) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
};
|
||
|
||
if (!find_comparison(check.left)) return false;
|
||
if (!find_comparison(check.right)) return false;
|
||
|
||
if (null_cmp && undefined_cmp && null_cmp !== undefined_cmp) {
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
def_optimize(AST_Conditional, function(self, compressor) {
|
||
if (!compressor.option("conditionals")) return self;
|
||
// This looks like lift_sequences(), should probably be under "sequences"
|
||
if (self.condition instanceof AST_Sequence) {
|
||
var expressions = self.condition.expressions.slice();
|
||
self.condition = expressions.pop();
|
||
expressions.push(self);
|
||
return make_sequence(self, expressions);
|
||
}
|
||
var cond = self.condition.evaluate(compressor);
|
||
if (cond !== self.condition) {
|
||
if (cond) {
|
||
return maintain_this_binding(compressor.parent(), compressor.self(), self.consequent);
|
||
} else {
|
||
return maintain_this_binding(compressor.parent(), compressor.self(), self.alternative);
|
||
}
|
||
}
|
||
var negated = cond.negate(compressor, first_in_statement(compressor));
|
||
if (best_of(compressor, cond, negated) === negated) {
|
||
self = make_node(AST_Conditional, self, {
|
||
condition: negated,
|
||
consequent: self.alternative,
|
||
alternative: self.consequent
|
||
});
|
||
}
|
||
var condition = self.condition;
|
||
var consequent = self.consequent;
|
||
var alternative = self.alternative;
|
||
// x?x:y --> x||y
|
||
if (condition instanceof AST_SymbolRef
|
||
&& consequent instanceof AST_SymbolRef
|
||
&& condition.definition() === consequent.definition()) {
|
||
return make_node(AST_Binary, self, {
|
||
operator: "||",
|
||
left: condition,
|
||
right: alternative
|
||
});
|
||
}
|
||
// if (foo) exp = something; else exp = something_else;
|
||
// |
|
||
// v
|
||
// exp = foo ? something : something_else;
|
||
if (
|
||
consequent instanceof AST_Assign
|
||
&& alternative instanceof AST_Assign
|
||
&& consequent.operator === alternative.operator
|
||
&& consequent.logical === alternative.logical
|
||
&& consequent.left.equivalent_to(alternative.left)
|
||
&& (!self.condition.has_side_effects(compressor)
|
||
|| consequent.operator == "="
|
||
&& !consequent.left.has_side_effects(compressor))
|
||
) {
|
||
return make_node(AST_Assign, self, {
|
||
operator: consequent.operator,
|
||
left: consequent.left,
|
||
logical: consequent.logical,
|
||
right: make_node(AST_Conditional, self, {
|
||
condition: self.condition,
|
||
consequent: consequent.right,
|
||
alternative: alternative.right
|
||
})
|
||
});
|
||
}
|
||
// x ? y(a) : y(b) --> y(x ? a : b)
|
||
var arg_index;
|
||
if (consequent instanceof AST_Call
|
||
&& alternative.TYPE === consequent.TYPE
|
||
&& consequent.args.length > 0
|
||
&& consequent.args.length == alternative.args.length
|
||
&& consequent.expression.equivalent_to(alternative.expression)
|
||
&& !self.condition.has_side_effects(compressor)
|
||
&& !consequent.expression.has_side_effects(compressor)
|
||
&& typeof (arg_index = single_arg_diff()) == "number") {
|
||
var node = consequent.clone();
|
||
node.args[arg_index] = make_node(AST_Conditional, self, {
|
||
condition: self.condition,
|
||
consequent: consequent.args[arg_index],
|
||
alternative: alternative.args[arg_index]
|
||
});
|
||
return node;
|
||
}
|
||
// a ? b : c ? b : d --> (a || c) ? b : d
|
||
if (alternative instanceof AST_Conditional
|
||
&& consequent.equivalent_to(alternative.consequent)) {
|
||
return make_node(AST_Conditional, self, {
|
||
condition: make_node(AST_Binary, self, {
|
||
operator: "||",
|
||
left: condition,
|
||
right: alternative.condition
|
||
}),
|
||
consequent: consequent,
|
||
alternative: alternative.alternative
|
||
}).optimize(compressor);
|
||
}
|
||
|
||
// a == null ? b : a -> a ?? b
|
||
if (
|
||
compressor.option("ecma") >= 2020 &&
|
||
is_nullish_check(condition, alternative, compressor)
|
||
) {
|
||
return make_node(AST_Binary, self, {
|
||
operator: "??",
|
||
left: alternative,
|
||
right: consequent
|
||
}).optimize(compressor);
|
||
}
|
||
|
||
// a ? b : (c, b) --> (a || c), b
|
||
if (alternative instanceof AST_Sequence
|
||
&& consequent.equivalent_to(alternative.expressions[alternative.expressions.length - 1])) {
|
||
return make_sequence(self, [
|
||
make_node(AST_Binary, self, {
|
||
operator: "||",
|
||
left: condition,
|
||
right: make_sequence(self, alternative.expressions.slice(0, -1))
|
||
}),
|
||
consequent
|
||
]).optimize(compressor);
|
||
}
|
||
// a ? b : (c && b) --> (a || c) && b
|
||
if (alternative instanceof AST_Binary
|
||
&& alternative.operator == "&&"
|
||
&& consequent.equivalent_to(alternative.right)) {
|
||
return make_node(AST_Binary, self, {
|
||
operator: "&&",
|
||
left: make_node(AST_Binary, self, {
|
||
operator: "||",
|
||
left: condition,
|
||
right: alternative.left
|
||
}),
|
||
right: consequent
|
||
}).optimize(compressor);
|
||
}
|
||
// x?y?z:a:a --> x&&y?z:a
|
||
if (consequent instanceof AST_Conditional
|
||
&& consequent.alternative.equivalent_to(alternative)) {
|
||
return make_node(AST_Conditional, self, {
|
||
condition: make_node(AST_Binary, self, {
|
||
left: self.condition,
|
||
operator: "&&",
|
||
right: consequent.condition
|
||
}),
|
||
consequent: consequent.consequent,
|
||
alternative: alternative
|
||
});
|
||
}
|
||
// x ? y : y --> x, y
|
||
if (consequent.equivalent_to(alternative)) {
|
||
return make_sequence(self, [
|
||
self.condition,
|
||
consequent
|
||
]).optimize(compressor);
|
||
}
|
||
// x ? y || z : z --> x && y || z
|
||
if (consequent instanceof AST_Binary
|
||
&& consequent.operator == "||"
|
||
&& consequent.right.equivalent_to(alternative)) {
|
||
return make_node(AST_Binary, self, {
|
||
operator: "||",
|
||
left: make_node(AST_Binary, self, {
|
||
operator: "&&",
|
||
left: self.condition,
|
||
right: consequent.left
|
||
}),
|
||
right: alternative
|
||
}).optimize(compressor);
|
||
}
|
||
|
||
const in_bool = compressor.in_boolean_context();
|
||
if (is_true(self.consequent)) {
|
||
if (is_false(self.alternative)) {
|
||
// c ? true : false ---> !!c
|
||
return booleanize(self.condition);
|
||
}
|
||
// c ? true : x ---> !!c || x
|
||
return make_node(AST_Binary, self, {
|
||
operator: "||",
|
||
left: booleanize(self.condition),
|
||
right: self.alternative
|
||
});
|
||
}
|
||
if (is_false(self.consequent)) {
|
||
if (is_true(self.alternative)) {
|
||
// c ? false : true ---> !c
|
||
return booleanize(self.condition.negate(compressor));
|
||
}
|
||
// c ? false : x ---> !c && x
|
||
return make_node(AST_Binary, self, {
|
||
operator: "&&",
|
||
left: booleanize(self.condition.negate(compressor)),
|
||
right: self.alternative
|
||
});
|
||
}
|
||
if (is_true(self.alternative)) {
|
||
// c ? x : true ---> !c || x
|
||
return make_node(AST_Binary, self, {
|
||
operator: "||",
|
||
left: booleanize(self.condition.negate(compressor)),
|
||
right: self.consequent
|
||
});
|
||
}
|
||
if (is_false(self.alternative)) {
|
||
// c ? x : false ---> !!c && x
|
||
return make_node(AST_Binary, self, {
|
||
operator: "&&",
|
||
left: booleanize(self.condition),
|
||
right: self.consequent
|
||
});
|
||
}
|
||
|
||
return self;
|
||
|
||
function booleanize(node) {
|
||
if (node.is_boolean()) return node;
|
||
// !!expression
|
||
return make_node(AST_UnaryPrefix, node, {
|
||
operator: "!",
|
||
expression: node.negate(compressor)
|
||
});
|
||
}
|
||
|
||
// AST_True or !0
|
||
function is_true(node) {
|
||
return node instanceof AST_True
|
||
|| in_bool
|
||
&& node instanceof AST_Constant
|
||
&& node.getValue()
|
||
|| (node instanceof AST_UnaryPrefix
|
||
&& node.operator == "!"
|
||
&& node.expression instanceof AST_Constant
|
||
&& !node.expression.getValue());
|
||
}
|
||
// AST_False or !1
|
||
function is_false(node) {
|
||
return node instanceof AST_False
|
||
|| in_bool
|
||
&& node instanceof AST_Constant
|
||
&& !node.getValue()
|
||
|| (node instanceof AST_UnaryPrefix
|
||
&& node.operator == "!"
|
||
&& node.expression instanceof AST_Constant
|
||
&& node.expression.getValue());
|
||
}
|
||
|
||
function single_arg_diff() {
|
||
var a = consequent.args;
|
||
var b = alternative.args;
|
||
for (var i = 0, len = a.length; i < len; i++) {
|
||
if (a[i] instanceof AST_Expansion) return;
|
||
if (!a[i].equivalent_to(b[i])) {
|
||
if (b[i] instanceof AST_Expansion) return;
|
||
for (var j = i + 1; j < len; j++) {
|
||
if (a[j] instanceof AST_Expansion) return;
|
||
if (!a[j].equivalent_to(b[j])) return;
|
||
}
|
||
return i;
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
def_optimize(AST_Boolean, function(self, compressor) {
|
||
if (compressor.in_boolean_context()) return make_node(AST_Number, self, {
|
||
value: +self.value
|
||
});
|
||
var p = compressor.parent();
|
||
if (compressor.option("booleans_as_integers")) {
|
||
if (p instanceof AST_Binary && (p.operator == "===" || p.operator == "!==")) {
|
||
p.operator = p.operator.replace(/=$/, "");
|
||
}
|
||
return make_node(AST_Number, self, {
|
||
value: +self.value
|
||
});
|
||
}
|
||
if (compressor.option("booleans")) {
|
||
if (p instanceof AST_Binary && (p.operator == "=="
|
||
|| p.operator == "!=")) {
|
||
return make_node(AST_Number, self, {
|
||
value: +self.value
|
||
});
|
||
}
|
||
return make_node(AST_UnaryPrefix, self, {
|
||
operator: "!",
|
||
expression: make_node(AST_Number, self, {
|
||
value: 1 - self.value
|
||
})
|
||
});
|
||
}
|
||
return self;
|
||
});
|
||
|
||
function safe_to_flatten(value, compressor) {
|
||
if (value instanceof AST_SymbolRef) {
|
||
value = value.fixed_value();
|
||
}
|
||
if (!value) return false;
|
||
if (!(value instanceof AST_Lambda || value instanceof AST_Class)) return true;
|
||
if (!(value instanceof AST_Lambda && value.contains_this())) return true;
|
||
return compressor.parent() instanceof AST_New;
|
||
}
|
||
|
||
AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) {
|
||
if (!compressor.option("properties")) return;
|
||
if (key === "__proto__") return;
|
||
if (this instanceof AST_DotHash) return;
|
||
|
||
var arrows = compressor.option("unsafe_arrows") && compressor.option("ecma") >= 2015;
|
||
var expr = this.expression;
|
||
if (expr instanceof AST_Object) {
|
||
var props = expr.properties;
|
||
|
||
for (var i = props.length; --i >= 0;) {
|
||
var prop = props[i];
|
||
|
||
if ("" + (prop instanceof AST_ConciseMethod ? prop.key.name : prop.key) == key) {
|
||
const all_props_flattenable = props.every((p) =>
|
||
(p instanceof AST_ObjectKeyVal
|
||
|| arrows && p instanceof AST_ConciseMethod && !p.value.is_generator
|
||
)
|
||
&& !p.computed_key()
|
||
);
|
||
|
||
if (!all_props_flattenable) return;
|
||
if (!safe_to_flatten(prop.value, compressor)) return;
|
||
|
||
return make_node(AST_Sub, this, {
|
||
expression: make_node(AST_Array, expr, {
|
||
elements: props.map(function(prop) {
|
||
var v = prop.value;
|
||
if (v instanceof AST_Accessor) {
|
||
v = make_node(AST_Function, v, v);
|
||
}
|
||
|
||
var k = prop.key;
|
||
if (k instanceof AST_Node && !(k instanceof AST_SymbolMethod)) {
|
||
return make_sequence(prop, [ k, v ]);
|
||
}
|
||
|
||
return v;
|
||
})
|
||
}),
|
||
property: make_node(AST_Number, this, {
|
||
value: i
|
||
})
|
||
});
|
||
}
|
||
}
|
||
}
|
||
});
|
||
|
||
def_optimize(AST_Sub, function(self, compressor) {
|
||
var expr = self.expression;
|
||
var prop = self.property;
|
||
if (compressor.option("properties")) {
|
||
var key = prop.evaluate(compressor);
|
||
if (key !== prop) {
|
||
if (typeof key == "string") {
|
||
if (key == "undefined") {
|
||
key = undefined;
|
||
} else {
|
||
var value = parseFloat(key);
|
||
if (value.toString() == key) {
|
||
key = value;
|
||
}
|
||
}
|
||
}
|
||
prop = self.property = best_of_expression(
|
||
prop,
|
||
make_node_from_constant(key, prop).transform(compressor)
|
||
);
|
||
var property = "" + key;
|
||
if (is_basic_identifier_string(property)
|
||
&& property.length <= prop.size() + 1) {
|
||
return make_node(AST_Dot, self, {
|
||
expression: expr,
|
||
optional: self.optional,
|
||
property: property,
|
||
quote: prop.quote,
|
||
}).optimize(compressor);
|
||
}
|
||
}
|
||
}
|
||
var fn;
|
||
OPT_ARGUMENTS: if (compressor.option("arguments")
|
||
&& expr instanceof AST_SymbolRef
|
||
&& expr.name == "arguments"
|
||
&& expr.definition().orig.length == 1
|
||
&& (fn = expr.scope) instanceof AST_Lambda
|
||
&& fn.uses_arguments
|
||
&& !(fn instanceof AST_Arrow)
|
||
&& prop instanceof AST_Number) {
|
||
var index = prop.getValue();
|
||
var params = new Set();
|
||
var argnames = fn.argnames;
|
||
for (var n = 0; n < argnames.length; n++) {
|
||
if (!(argnames[n] instanceof AST_SymbolFunarg)) {
|
||
break OPT_ARGUMENTS; // destructuring parameter - bail
|
||
}
|
||
var param = argnames[n].name;
|
||
if (params.has(param)) {
|
||
break OPT_ARGUMENTS; // duplicate parameter - bail
|
||
}
|
||
params.add(param);
|
||
}
|
||
var argname = fn.argnames[index];
|
||
if (argname && compressor.has_directive("use strict")) {
|
||
var def = argname.definition();
|
||
if (!compressor.option("reduce_vars") || def.assignments || def.orig.length > 1) {
|
||
argname = null;
|
||
}
|
||
} else if (!argname && !compressor.option("keep_fargs") && index < fn.argnames.length + 5) {
|
||
while (index >= fn.argnames.length) {
|
||
argname = fn.create_symbol(AST_SymbolFunarg, {
|
||
source: fn,
|
||
scope: fn,
|
||
tentative_name: "argument_" + fn.argnames.length,
|
||
});
|
||
fn.argnames.push(argname);
|
||
}
|
||
}
|
||
if (argname) {
|
||
var sym = make_node(AST_SymbolRef, self, argname);
|
||
sym.reference({});
|
||
clear_flag(argname, UNUSED);
|
||
return sym;
|
||
}
|
||
}
|
||
if (compressor.is_lhs()) return self;
|
||
if (key !== prop) {
|
||
var sub = self.flatten_object(property, compressor);
|
||
if (sub) {
|
||
expr = self.expression = sub.expression;
|
||
prop = self.property = sub.property;
|
||
}
|
||
}
|
||
if (compressor.option("properties") && compressor.option("side_effects")
|
||
&& prop instanceof AST_Number && expr instanceof AST_Array) {
|
||
var index = prop.getValue();
|
||
var elements = expr.elements;
|
||
var retValue = elements[index];
|
||
FLATTEN: if (safe_to_flatten(retValue, compressor)) {
|
||
var flatten = true;
|
||
var values = [];
|
||
for (var i = elements.length; --i > index;) {
|
||
var value = elements[i].drop_side_effect_free(compressor);
|
||
if (value) {
|
||
values.unshift(value);
|
||
if (flatten && value.has_side_effects(compressor)) flatten = false;
|
||
}
|
||
}
|
||
if (retValue instanceof AST_Expansion) break FLATTEN;
|
||
retValue = retValue instanceof AST_Hole ? make_void_0(retValue) : retValue;
|
||
if (!flatten) values.unshift(retValue);
|
||
while (--i >= 0) {
|
||
var value = elements[i];
|
||
if (value instanceof AST_Expansion) break FLATTEN;
|
||
value = value.drop_side_effect_free(compressor);
|
||
if (value) values.unshift(value);
|
||
else index--;
|
||
}
|
||
if (flatten) {
|
||
values.push(retValue);
|
||
return make_sequence(self, values).optimize(compressor);
|
||
} else return make_node(AST_Sub, self, {
|
||
expression: make_node(AST_Array, expr, {
|
||
elements: values
|
||
}),
|
||
property: make_node(AST_Number, prop, {
|
||
value: index
|
||
})
|
||
});
|
||
}
|
||
}
|
||
var ev = self.evaluate(compressor);
|
||
if (ev !== self) {
|
||
ev = make_node_from_constant(ev, self).optimize(compressor);
|
||
return best_of(compressor, ev, self);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Chain, function (self, compressor) {
|
||
if (is_nullish(self.expression, compressor)) {
|
||
let parent = compressor.parent();
|
||
// It's valid to delete a nullish optional chain, but if we optimized
|
||
// this to `delete undefined` then it would appear to be a syntax error
|
||
// when we try to optimize the delete. Thankfully, `delete 0` is fine.
|
||
if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") {
|
||
return make_node_from_constant(0, self);
|
||
}
|
||
return make_void_0(self).optimize(compressor);
|
||
}
|
||
if (
|
||
self.expression instanceof AST_PropAccess
|
||
|| self.expression instanceof AST_Call
|
||
) {
|
||
return self;
|
||
} else {
|
||
// Keep the AST valid, in case the child swapped itself
|
||
return self.expression;
|
||
}
|
||
});
|
||
|
||
def_optimize(AST_Dot, function(self, compressor) {
|
||
const parent = compressor.parent();
|
||
if (compressor.is_lhs()) return self;
|
||
if (compressor.option("unsafe_proto")
|
||
&& self.expression instanceof AST_Dot
|
||
&& self.expression.property == "prototype") {
|
||
var exp = self.expression.expression;
|
||
if (is_undeclared_ref(exp)) switch (exp.name) {
|
||
case "Array":
|
||
self.expression = make_node(AST_Array, self.expression, {
|
||
elements: []
|
||
});
|
||
break;
|
||
case "Function":
|
||
self.expression = make_empty_function(self.expression);
|
||
break;
|
||
case "Number":
|
||
self.expression = make_node(AST_Number, self.expression, {
|
||
value: 0
|
||
});
|
||
break;
|
||
case "Object":
|
||
self.expression = make_node(AST_Object, self.expression, {
|
||
properties: []
|
||
});
|
||
break;
|
||
case "RegExp":
|
||
self.expression = make_node(AST_RegExp, self.expression, {
|
||
value: { source: "t", flags: "" }
|
||
});
|
||
break;
|
||
case "String":
|
||
self.expression = make_node(AST_String, self.expression, {
|
||
value: ""
|
||
});
|
||
break;
|
||
}
|
||
}
|
||
if (!(parent instanceof AST_Call) || !has_annotation(parent, _NOINLINE)) {
|
||
const sub = self.flatten_object(self.property, compressor);
|
||
if (sub) return sub.optimize(compressor);
|
||
}
|
||
|
||
if (self.expression instanceof AST_PropAccess
|
||
&& parent instanceof AST_PropAccess) {
|
||
return self;
|
||
}
|
||
|
||
let ev = self.evaluate(compressor);
|
||
if (ev !== self) {
|
||
ev = make_node_from_constant(ev, self).optimize(compressor);
|
||
return best_of(compressor, ev, self);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
function literals_in_boolean_context(self, compressor) {
|
||
if (compressor.in_boolean_context()) {
|
||
return best_of(compressor, self, make_sequence(self, [
|
||
self,
|
||
make_node(AST_True, self)
|
||
]).optimize(compressor));
|
||
}
|
||
return self;
|
||
}
|
||
|
||
function inline_array_like_spread(elements) {
|
||
for (var i = 0; i < elements.length; i++) {
|
||
var el = elements[i];
|
||
if (el instanceof AST_Expansion) {
|
||
var expr = el.expression;
|
||
if (
|
||
expr instanceof AST_Array
|
||
&& !expr.elements.some(elm => elm instanceof AST_Hole)
|
||
) {
|
||
elements.splice(i, 1, ...expr.elements);
|
||
// Step back one, as the element at i is now new.
|
||
i--;
|
||
}
|
||
// In array-like spread, spreading a non-iterable value is TypeError.
|
||
// We therefore can’t optimize anything else, unlike with object spread.
|
||
}
|
||
}
|
||
}
|
||
|
||
def_optimize(AST_Array, function(self, compressor) {
|
||
var optimized = literals_in_boolean_context(self, compressor);
|
||
if (optimized !== self) {
|
||
return optimized;
|
||
}
|
||
inline_array_like_spread(self.elements);
|
||
return self;
|
||
});
|
||
|
||
function inline_object_prop_spread(props) {
|
||
for (var i = 0; i < props.length; i++) {
|
||
var prop = props[i];
|
||
if (prop instanceof AST_Expansion) {
|
||
const expr = prop.expression;
|
||
if (
|
||
expr instanceof AST_Object
|
||
&& expr.properties.every(prop => prop instanceof AST_ObjectKeyVal)
|
||
) {
|
||
props.splice(i, 1, ...expr.properties);
|
||
// Step back one, as the property at i is now new.
|
||
i--;
|
||
} else if ((
|
||
// `expr.is_constant()` returns `false` for `AST_RegExp`, so need both.
|
||
expr instanceof AST_Constant
|
||
|| expr.is_constant()
|
||
) && !(expr instanceof AST_String)) {
|
||
// Unlike array-like spread, in object spread, spreading a
|
||
// non-iterable value silently does nothing; it is thus safe
|
||
// to remove. AST_String is the only iterable constant.
|
||
props.splice(i, 1);
|
||
i--;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
def_optimize(AST_Object, function(self, compressor) {
|
||
var optimized = literals_in_boolean_context(self, compressor);
|
||
if (optimized !== self) {
|
||
return optimized;
|
||
}
|
||
inline_object_prop_spread(self.properties);
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_RegExp, literals_in_boolean_context);
|
||
|
||
def_optimize(AST_Return, function(self, compressor) {
|
||
if (self.value && is_undefined(self.value, compressor)) {
|
||
self.value = null;
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Arrow, opt_AST_Lambda);
|
||
|
||
def_optimize(AST_Function, function(self, compressor) {
|
||
self = opt_AST_Lambda(self, compressor);
|
||
if (compressor.option("unsafe_arrows")
|
||
&& compressor.option("ecma") >= 2015
|
||
&& !self.name
|
||
&& !self.is_generator
|
||
&& !self.uses_arguments
|
||
&& !self.pinned()) {
|
||
const uses_this = walk(self, node => {
|
||
if (node instanceof AST_This) return walk_abort;
|
||
});
|
||
if (!uses_this) return make_node(AST_Arrow, self, self).optimize(compressor);
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Class, function(self) {
|
||
for (let i = 0; i < self.properties.length; i++) {
|
||
const prop = self.properties[i];
|
||
if (prop instanceof AST_ClassStaticBlock && prop.body.length == 0) {
|
||
self.properties.splice(i, 1);
|
||
i--;
|
||
}
|
||
}
|
||
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_ClassStaticBlock, function(self, compressor) {
|
||
tighten_body(self.body, compressor);
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Yield, function(self, compressor) {
|
||
if (self.expression && !self.is_star && is_undefined(self.expression, compressor)) {
|
||
self.expression = null;
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_TemplateString, function(self, compressor) {
|
||
if (
|
||
!compressor.option("evaluate")
|
||
|| compressor.parent() instanceof AST_PrefixedTemplateString
|
||
) {
|
||
return self;
|
||
}
|
||
|
||
var segments = [];
|
||
for (var i = 0; i < self.segments.length; i++) {
|
||
var segment = self.segments[i];
|
||
if (segment instanceof AST_Node) {
|
||
var result = segment.evaluate(compressor);
|
||
// Evaluate to constant value
|
||
// Constant value shorter than ${segment}
|
||
if (result !== segment && (result + "").length <= segment.size() + "${}".length) {
|
||
// There should always be a previous and next segment if segment is a node
|
||
segments[segments.length - 1].value = segments[segments.length - 1].value + result + self.segments[++i].value;
|
||
continue;
|
||
}
|
||
// `before ${`innerBefore ${any} innerAfter`} after` => `before innerBefore ${any} innerAfter after`
|
||
// TODO:
|
||
// `before ${'test' + foo} after` => `before innerBefore ${any} innerAfter after`
|
||
// `before ${foo + 'test} after` => `before innerBefore ${any} innerAfter after`
|
||
if (segment instanceof AST_TemplateString) {
|
||
var inners = segment.segments;
|
||
segments[segments.length - 1].value += inners[0].value;
|
||
for (var j = 1; j < inners.length; j++) {
|
||
segment = inners[j];
|
||
segments.push(segment);
|
||
}
|
||
continue;
|
||
}
|
||
}
|
||
segments.push(segment);
|
||
}
|
||
self.segments = segments;
|
||
|
||
// `foo` => "foo"
|
||
if (segments.length == 1) {
|
||
return make_node(AST_String, self, segments[0]);
|
||
}
|
||
|
||
if (
|
||
segments.length === 3
|
||
&& segments[1] instanceof AST_Node
|
||
&& (
|
||
segments[1].is_string(compressor)
|
||
|| segments[1].is_number_or_bigint(compressor)
|
||
|| is_nullish(segments[1], compressor)
|
||
|| compressor.option("unsafe")
|
||
)
|
||
) {
|
||
// `foo${bar}` => "foo" + bar
|
||
if (segments[2].value === "") {
|
||
return make_node(AST_Binary, self, {
|
||
operator: "+",
|
||
left: make_node(AST_String, self, {
|
||
value: segments[0].value,
|
||
}),
|
||
right: segments[1],
|
||
});
|
||
}
|
||
// `${bar}baz` => bar + "baz"
|
||
if (segments[0].value === "") {
|
||
return make_node(AST_Binary, self, {
|
||
operator: "+",
|
||
left: segments[1],
|
||
right: make_node(AST_String, self, {
|
||
value: segments[2].value,
|
||
}),
|
||
});
|
||
}
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_PrefixedTemplateString, function(self) {
|
||
return self;
|
||
});
|
||
|
||
// ["p"]:1 ---> p:1
|
||
// [42]:1 ---> 42:1
|
||
function lift_key(self, compressor) {
|
||
if (!compressor.option("computed_props")) return self;
|
||
// save a comparison in the typical case
|
||
if (!(self.key instanceof AST_Constant)) return self;
|
||
// allow certain acceptable props as not all AST_Constants are true constants
|
||
if (self.key instanceof AST_String || self.key instanceof AST_Number) {
|
||
const key = self.key.value.toString();
|
||
|
||
if (key === "__proto__") return self;
|
||
if (key == "constructor"
|
||
&& compressor.parent() instanceof AST_Class) return self;
|
||
if (self instanceof AST_ObjectKeyVal) {
|
||
self.quote = self.key.quote;
|
||
self.key = key;
|
||
} else if (self instanceof AST_ClassProperty) {
|
||
self.quote = self.key.quote;
|
||
self.key = make_node(AST_SymbolClassProperty, self.key, {
|
||
name: key,
|
||
});
|
||
} else {
|
||
self.quote = self.key.quote;
|
||
self.key = make_node(AST_SymbolMethod, self.key, {
|
||
name: key,
|
||
});
|
||
}
|
||
}
|
||
return self;
|
||
}
|
||
|
||
def_optimize(AST_ObjectProperty, lift_key);
|
||
|
||
def_optimize(AST_ConciseMethod, function(self, compressor) {
|
||
lift_key(self, compressor);
|
||
// p(){return x;} ---> p:()=>x
|
||
if (compressor.option("arrows")
|
||
&& compressor.parent() instanceof AST_Object
|
||
&& !self.value.is_generator
|
||
&& !self.value.uses_arguments
|
||
&& !self.value.pinned()
|
||
&& self.value.body.length == 1
|
||
&& self.value.body[0] instanceof AST_Return
|
||
&& self.value.body[0].value
|
||
&& !self.value.contains_this()) {
|
||
var arrow = make_node(AST_Arrow, self.value, self.value);
|
||
arrow.async = self.value.async;
|
||
arrow.is_generator = self.value.is_generator;
|
||
return make_node(AST_ObjectKeyVal, self, {
|
||
key: self.key instanceof AST_SymbolMethod ? self.key.name : self.key,
|
||
value: arrow,
|
||
quote: self.quote,
|
||
});
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_ObjectKeyVal, function(self, compressor) {
|
||
lift_key(self, compressor);
|
||
// p:function(){} ---> p(){}
|
||
// p:function*(){} ---> *p(){}
|
||
// p:async function(){} ---> async p(){}
|
||
// p:()=>{} ---> p(){}
|
||
// p:async()=>{} ---> async p(){}
|
||
var unsafe_methods = compressor.option("unsafe_methods");
|
||
if (unsafe_methods
|
||
&& compressor.option("ecma") >= 2015
|
||
&& (!(unsafe_methods instanceof RegExp) || unsafe_methods.test(self.key + ""))) {
|
||
var key = self.key;
|
||
var value = self.value;
|
||
var is_arrow_with_block = value instanceof AST_Arrow
|
||
&& Array.isArray(value.body)
|
||
&& !value.contains_this();
|
||
if ((is_arrow_with_block || value instanceof AST_Function) && !value.name) {
|
||
return make_node(AST_ConciseMethod, self, {
|
||
key: key instanceof AST_Node ? key : make_node(AST_SymbolMethod, self, {
|
||
name: key,
|
||
}),
|
||
value: make_node(AST_Accessor, value, value),
|
||
quote: self.quote,
|
||
});
|
||
}
|
||
}
|
||
return self;
|
||
});
|
||
|
||
def_optimize(AST_Destructuring, function(self, compressor) {
|
||
if (compressor.option("pure_getters") == true
|
||
&& compressor.option("unused")
|
||
&& !self.is_array
|
||
&& Array.isArray(self.names)
|
||
&& !is_destructuring_export_decl(compressor)
|
||
&& !(self.names[self.names.length - 1] instanceof AST_Expansion)) {
|
||
var keep = [];
|
||
for (var i = 0; i < self.names.length; i++) {
|
||
var elem = self.names[i];
|
||
if (!(elem instanceof AST_ObjectKeyVal
|
||
&& typeof elem.key == "string"
|
||
&& elem.value instanceof AST_SymbolDeclaration
|
||
&& !should_retain(compressor, elem.value.definition()))) {
|
||
keep.push(elem);
|
||
}
|
||
}
|
||
if (keep.length != self.names.length) {
|
||
self.names = keep;
|
||
}
|
||
}
|
||
return self;
|
||
|
||
function is_destructuring_export_decl(compressor) {
|
||
var ancestors = [/^VarDef$/, /^(Const|Let|Var)$/, /^Export$/];
|
||
for (var a = 0, p = 0, len = ancestors.length; a < len; p++) {
|
||
var parent = compressor.parent(p);
|
||
if (!parent) return false;
|
||
if (a === 0 && parent.TYPE == "Destructuring") continue;
|
||
if (!ancestors[a].test(parent.TYPE)) {
|
||
return false;
|
||
}
|
||
a++;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
function should_retain(compressor, def) {
|
||
if (def.references.length) return true;
|
||
if (!def.global) return false;
|
||
if (compressor.toplevel.vars) {
|
||
if (compressor.top_retain) {
|
||
return compressor.top_retain(def);
|
||
}
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
});
|
||
|
||
export {
|
||
Compressor,
|
||
};
|