Buckets:
arudradey/ml-cpu-storage / emsdk /upstream /emscripten /node_modules /terser /lib /compress /inline.js
| /*********************************************************************** | |
| 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_Array, | |
| AST_Assign, | |
| AST_Block, | |
| AST_Call, | |
| AST_Catch, | |
| AST_Class, | |
| AST_ClassExpression, | |
| AST_DefaultAssign, | |
| AST_DefClass, | |
| AST_Defun, | |
| AST_Destructuring, | |
| AST_EmptyStatement, | |
| AST_Expansion, | |
| AST_Export, | |
| AST_Function, | |
| AST_IterationStatement, | |
| AST_Lambda, | |
| AST_Node, | |
| AST_Number, | |
| AST_Object, | |
| AST_ObjectKeyVal, | |
| AST_PropAccess, | |
| AST_Return, | |
| AST_Scope, | |
| AST_SimpleStatement, | |
| AST_Statement, | |
| AST_SymbolDefun, | |
| AST_SymbolFunarg, | |
| AST_SymbolLambda, | |
| AST_SymbolRef, | |
| AST_SymbolVar, | |
| AST_This, | |
| AST_Toplevel, | |
| AST_UnaryPrefix, | |
| AST_Undefined, | |
| AST_Var, | |
| AST_VarDef, | |
| walk, | |
| _INLINE, | |
| _NOINLINE, | |
| _PURE, | |
| } from "../ast.js"; | |
| import { make_node, has_annotation } from "../utils/index.js"; | |
| import "../size.js"; | |
| import "./evaluate.js"; | |
| import "./drop-side-effect-free.js"; | |
| import "./reduce-vars.js"; | |
| import { | |
| SQUEEZED, | |
| INLINED, | |
| UNUSED, | |
| has_flag, | |
| set_flag, | |
| } from "./compressor-flags.js"; | |
| import { | |
| make_sequence, | |
| best_of, | |
| make_node_from_constant, | |
| identifier_atom, | |
| is_empty, | |
| is_func_expr, | |
| is_iife_call, | |
| is_reachable, | |
| is_recursive_ref, | |
| retain_top_func, | |
| } from "./common.js"; | |
| /** | |
| * Module that contains the inlining logic. | |
| * | |
| * @module | |
| * | |
| * The stars of the show are `inline_into_symbolref` and `inline_into_call`. | |
| */ | |
| function within_array_or_object_literal(compressor) { | |
| var node, level = 0; | |
| while (node = compressor.parent(level++)) { | |
| if (node instanceof AST_Statement) return false; | |
| if (node instanceof AST_Array | |
| || node instanceof AST_ObjectKeyVal | |
| || node instanceof AST_Object) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| function scope_encloses_variables_in_this_scope(scope, pulled_scope) { | |
| for (const enclosed of pulled_scope.enclosed) { | |
| if (pulled_scope.variables.has(enclosed.name)) { | |
| continue; | |
| } | |
| const looked_up = scope.find_variable(enclosed.name); | |
| if (looked_up) { | |
| if (looked_up === enclosed) continue; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * An extra check function for `top_retain` option, compare the length of const identifier | |
| * and init value length and return true if init value is longer than identifier. for example: | |
| * ``` | |
| * // top_retain: ["example"] | |
| * const example = 100 | |
| * ``` | |
| * it will return false because length of "100" is short than identifier "example". | |
| */ | |
| function is_const_symbol_short_than_init_value(def, fixed_value) { | |
| if (def.orig.length === 1 && fixed_value) { | |
| const init_value_length = fixed_value.size(); | |
| const identifer_length = def.name.length; | |
| return init_value_length > identifer_length; | |
| } | |
| return true; | |
| } | |
| export function inline_into_symbolref(self, compressor) { | |
| if (compressor.in_computed_key()) return self; | |
| const parent = compressor.parent(); | |
| const def = self.definition(); | |
| const nearest_scope = compressor.find_scope(); | |
| let fixed = self.fixed_value(); | |
| if ( | |
| compressor.top_retain && | |
| def.global && | |
| compressor.top_retain(def) && | |
| // when identifier is in top_retain option dose not mean we can always inline it. | |
| // if identifier name is longer then init value, we can replace it. | |
| is_const_symbol_short_than_init_value(def, fixed) | |
| ) { | |
| // keep it | |
| def.fixed = false; | |
| def.single_use = false; | |
| return self; | |
| } | |
| let single_use = def.single_use | |
| && !(parent instanceof AST_Call | |
| && (parent.is_callee_pure(compressor)) | |
| || has_annotation(parent, _NOINLINE)) | |
| && !(parent instanceof AST_Export | |
| && fixed instanceof AST_Lambda | |
| && fixed.name); | |
| if (single_use && fixed instanceof AST_Node) { | |
| single_use = | |
| !fixed.has_side_effects(compressor) | |
| && !fixed.may_throw(compressor); | |
| } | |
| if (fixed instanceof AST_Class && def.scope !== self.scope) { | |
| return self; | |
| } | |
| if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) { | |
| if (retain_top_func(fixed, compressor)) { | |
| single_use = false; | |
| } else if (def.scope !== self.scope | |
| && (def.escaped == 1 | |
| || has_flag(fixed, INLINED) | |
| || within_array_or_object_literal(compressor) | |
| || !compressor.option("reduce_funcs"))) { | |
| single_use = false; | |
| } else if (is_recursive_ref(compressor, def)) { | |
| single_use = false; | |
| } else if (def.scope !== self.scope || def.orig[0] instanceof AST_SymbolFunarg) { | |
| single_use = fixed.is_constant_expression(self.scope); | |
| if (single_use == "f") { | |
| var scope = self.scope; | |
| do { | |
| if (scope instanceof AST_Defun || is_func_expr(scope)) { | |
| set_flag(scope, INLINED); | |
| } | |
| } while (scope = scope.parent_scope); | |
| } | |
| } | |
| } | |
| if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) { | |
| single_use = | |
| def.scope === self.scope | |
| && !scope_encloses_variables_in_this_scope(nearest_scope, fixed) | |
| || parent instanceof AST_Call | |
| && parent.expression === self | |
| && !scope_encloses_variables_in_this_scope(nearest_scope, fixed) | |
| && !(fixed.name && fixed.name.definition().recursive_refs > 0); | |
| } | |
| if (single_use && fixed) { | |
| if (fixed instanceof AST_DefClass) { | |
| set_flag(fixed, SQUEEZED); | |
| fixed = make_node(AST_ClassExpression, fixed, fixed); | |
| } | |
| if (fixed instanceof AST_Defun) { | |
| set_flag(fixed, SQUEEZED); | |
| fixed = make_node(AST_Function, fixed, fixed); | |
| } | |
| if (def.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) { | |
| const defun_def = fixed.name.definition(); | |
| let lambda_def = fixed.variables.get(fixed.name.name); | |
| let name = lambda_def && lambda_def.orig[0]; | |
| if (!(name instanceof AST_SymbolLambda)) { | |
| name = make_node(AST_SymbolLambda, fixed.name, fixed.name); | |
| name.scope = fixed; | |
| fixed.name = name; | |
| lambda_def = fixed.def_function(name); | |
| } | |
| walk(fixed, node => { | |
| if (node instanceof AST_SymbolRef && node.definition() === defun_def) { | |
| node.thedef = lambda_def; | |
| lambda_def.references.push(node); | |
| } | |
| }); | |
| } | |
| if ( | |
| (fixed instanceof AST_Lambda || fixed instanceof AST_Class) | |
| && fixed.parent_scope !== nearest_scope | |
| ) { | |
| fixed = fixed.clone(true, compressor.get_toplevel()); | |
| nearest_scope.add_child_scope(fixed); | |
| } | |
| return fixed.optimize(compressor); | |
| } | |
| // multiple uses | |
| if (fixed) { | |
| let replace; | |
| if (fixed instanceof AST_This) { | |
| if (!(def.orig[0] instanceof AST_SymbolFunarg) | |
| && def.references.every((ref) => | |
| def.scope === ref.scope | |
| )) { | |
| replace = fixed; | |
| } | |
| } else { | |
| var ev = fixed.evaluate(compressor); | |
| if ( | |
| ev !== fixed | |
| && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp)) | |
| ) { | |
| replace = make_node_from_constant(ev, fixed); | |
| } | |
| } | |
| if (replace) { | |
| const name_length = self.size(compressor); | |
| const replace_size = replace.size(compressor); | |
| let overhead = 0; | |
| if (compressor.option("unused") && !compressor.exposed(def)) { | |
| overhead = | |
| (name_length + 2 + fixed.size(compressor)) / | |
| (def.references.length - def.assignments); | |
| } | |
| if (replace_size <= name_length + overhead) { | |
| return replace; | |
| } | |
| } | |
| } | |
| return self; | |
| } | |
| export function inline_into_call(self, compressor) { | |
| if (compressor.in_computed_key()) return self; | |
| var exp = self.expression; | |
| var fn = exp; | |
| var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion)); | |
| if (compressor.option("reduce_vars") | |
| && fn instanceof AST_SymbolRef | |
| && !has_annotation(self, _NOINLINE) | |
| ) { | |
| const fixed = fn.fixed_value(); | |
| if ( | |
| retain_top_func(fixed, compressor) | |
| || !compressor.toplevel.funcs && exp.definition().global | |
| ) { | |
| return self; | |
| } | |
| fn = fixed; | |
| } | |
| var is_func = fn instanceof AST_Lambda; | |
| var stat = is_func && fn.body[0]; | |
| var is_regular_func = is_func && !fn.is_generator && !fn.async; | |
| var can_inline = is_regular_func && compressor.option("inline") && !self.is_callee_pure(compressor); | |
| if (can_inline && stat instanceof AST_Return) { | |
| let returned = stat.value; | |
| if (!returned || returned.is_constant_expression()) { | |
| if (returned) { | |
| returned = returned.clone(true); | |
| } else { | |
| returned = make_node(AST_Undefined, self); | |
| } | |
| const args = self.args.concat(returned); | |
| return make_sequence(self, args).optimize(compressor); | |
| } | |
| // optimize identity function | |
| if ( | |
| fn.argnames.length === 1 | |
| && (fn.argnames[0] instanceof AST_SymbolFunarg) | |
| && self.args.length < 2 | |
| && !(self.args[0] instanceof AST_Expansion) | |
| && returned instanceof AST_SymbolRef | |
| && returned.name === fn.argnames[0].name | |
| ) { | |
| const replacement = | |
| (self.args[0] || make_node(AST_Undefined)).optimize(compressor); | |
| let parent; | |
| if ( | |
| replacement instanceof AST_PropAccess | |
| && (parent = compressor.parent()) instanceof AST_Call | |
| && parent.expression === self | |
| ) { | |
| // identity function was being used to remove `this`, like in | |
| // | |
| // id(bag.no_this)(...) | |
| // | |
| // Replace with a larger but more effish (0, bag.no_this) wrapper. | |
| return make_sequence(self, [ | |
| make_node(AST_Number, self, { value: 0 }), | |
| replacement | |
| ]); | |
| } | |
| // replace call with first argument or undefined if none passed | |
| return replacement; | |
| } | |
| } | |
| if (can_inline) { | |
| var scope, in_loop, level = -1; | |
| let def; | |
| let returned_value; | |
| let nearest_scope; | |
| if (simple_args | |
| && !fn.uses_arguments | |
| && !(compressor.parent() instanceof AST_Class) | |
| && !(fn.name && fn instanceof AST_Function) | |
| && (returned_value = can_flatten_body(stat)) | |
| && (exp === fn | |
| || has_annotation(self, _INLINE) | |
| || compressor.option("unused") | |
| && (def = exp.definition()).references.length == 1 | |
| && !is_recursive_ref(compressor, def) | |
| && fn.is_constant_expression(exp.scope)) | |
| && !has_annotation(self, _PURE | _NOINLINE) | |
| && !fn.contains_this() | |
| && can_inject_symbols() | |
| && (nearest_scope = compressor.find_scope()) | |
| && !scope_encloses_variables_in_this_scope(nearest_scope, fn) | |
| && !(function in_default_assign() { | |
| // Due to the fact function parameters have their own scope | |
| // which can't use `var something` in the function body within, | |
| // we simply don't inline into DefaultAssign. | |
| let i = 0; | |
| let p; | |
| while ((p = compressor.parent(i++))) { | |
| if (p instanceof AST_DefaultAssign) return true; | |
| if (p instanceof AST_Block) break; | |
| } | |
| return false; | |
| })() | |
| && !(scope instanceof AST_Class) | |
| ) { | |
| set_flag(fn, SQUEEZED); | |
| nearest_scope.add_child_scope(fn); | |
| return make_sequence(self, flatten_fn(returned_value)).optimize(compressor); | |
| } | |
| } | |
| if (can_inline && has_annotation(self, _INLINE)) { | |
| set_flag(fn, SQUEEZED); | |
| fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn); | |
| fn = fn.clone(true); | |
| fn.figure_out_scope({}, { | |
| parent_scope: compressor.find_scope(), | |
| toplevel: compressor.get_toplevel() | |
| }); | |
| return make_node(AST_Call, self, { | |
| expression: fn, | |
| args: self.args, | |
| }).optimize(compressor); | |
| } | |
| const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty); | |
| if (can_drop_this_call) { | |
| var args = self.args.concat(make_node(AST_Undefined, self)); | |
| return make_sequence(self, args).optimize(compressor); | |
| } | |
| if (compressor.option("negate_iife") | |
| && compressor.parent() instanceof AST_SimpleStatement | |
| && is_iife_call(self)) { | |
| return self.negate(compressor, true); | |
| } | |
| 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; | |
| function return_value(stat) { | |
| if (!stat) return make_node(AST_Undefined, self); | |
| if (stat instanceof AST_Return) { | |
| if (!stat.value) return make_node(AST_Undefined, self); | |
| return stat.value.clone(true); | |
| } | |
| if (stat instanceof AST_SimpleStatement) { | |
| return make_node(AST_UnaryPrefix, stat, { | |
| operator: "void", | |
| expression: stat.body.clone(true) | |
| }); | |
| } | |
| } | |
| function can_flatten_body(stat) { | |
| var body = fn.body; | |
| var len = body.length; | |
| if (compressor.option("inline") < 3) { | |
| return len == 1 && return_value(stat); | |
| } | |
| stat = null; | |
| for (var i = 0; i < len; i++) { | |
| var line = body[i]; | |
| if (line instanceof AST_Var) { | |
| if (stat && !line.definitions.every((var_def) => | |
| !var_def.value | |
| )) { | |
| return false; | |
| } | |
| } else if (stat) { | |
| return false; | |
| } else if (!(line instanceof AST_EmptyStatement)) { | |
| stat = line; | |
| } | |
| } | |
| return return_value(stat); | |
| } | |
| function can_inject_args(block_scoped, safe_to_inject) { | |
| for (var i = 0, len = fn.argnames.length; i < len; i++) { | |
| var arg = fn.argnames[i]; | |
| if (arg instanceof AST_DefaultAssign) { | |
| if (has_flag(arg.left, UNUSED)) continue; | |
| return false; | |
| } | |
| if (arg instanceof AST_Destructuring) return false; | |
| if (arg instanceof AST_Expansion) { | |
| if (has_flag(arg.expression, UNUSED)) continue; | |
| return false; | |
| } | |
| if (has_flag(arg, UNUSED)) continue; | |
| if (!safe_to_inject | |
| || block_scoped.has(arg.name) | |
| || identifier_atom.has(arg.name) | |
| || scope.conflicting_def(arg.name)) { | |
| return false; | |
| } | |
| if (in_loop) in_loop.push(arg.definition()); | |
| } | |
| return true; | |
| } | |
| function can_inject_vars(block_scoped, safe_to_inject) { | |
| var len = fn.body.length; | |
| for (var i = 0; i < len; i++) { | |
| var stat = fn.body[i]; | |
| if (!(stat instanceof AST_Var)) continue; | |
| if (!safe_to_inject) return false; | |
| for (var j = stat.definitions.length; --j >= 0;) { | |
| var name = stat.definitions[j].name; | |
| if (name instanceof AST_Destructuring | |
| || block_scoped.has(name.name) | |
| || identifier_atom.has(name.name) | |
| || scope.conflicting_def(name.name)) { | |
| return false; | |
| } | |
| if (in_loop) in_loop.push(name.definition()); | |
| } | |
| } | |
| return true; | |
| } | |
| function can_inject_symbols() { | |
| var block_scoped = new Set(); | |
| do { | |
| scope = compressor.parent(++level); | |
| if (scope.is_block_scope() && scope.block_scope) { | |
| // TODO this is sometimes undefined during compression. | |
| // But it should always have a value! | |
| scope.block_scope.variables.forEach(function (variable) { | |
| block_scoped.add(variable.name); | |
| }); | |
| } | |
| if (scope instanceof AST_Catch) { | |
| // TODO can we delete? AST_Catch is a block scope. | |
| if (scope.argname) { | |
| block_scoped.add(scope.argname.name); | |
| } | |
| } else if (scope instanceof AST_IterationStatement) { | |
| in_loop = []; | |
| } else if (scope instanceof AST_SymbolRef) { | |
| if (scope.fixed_value() instanceof AST_Scope) return false; | |
| } | |
| } while (!(scope instanceof AST_Scope)); | |
| var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars; | |
| var inline = compressor.option("inline"); | |
| if (!can_inject_vars(block_scoped, inline >= 3 && safe_to_inject)) return false; | |
| if (!can_inject_args(block_scoped, inline >= 2 && safe_to_inject)) return false; | |
| return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop); | |
| } | |
| function append_var(decls, expressions, name, value) { | |
| var def = name.definition(); | |
| // Name already exists, only when a function argument had the same name | |
| const already_appended = scope.variables.has(name.name); | |
| if (!already_appended) { | |
| scope.variables.set(name.name, def); | |
| scope.enclosed.push(def); | |
| decls.push(make_node(AST_VarDef, name, { | |
| name: name, | |
| value: null | |
| })); | |
| } | |
| var sym = make_node(AST_SymbolRef, name, name); | |
| def.references.push(sym); | |
| if (value) expressions.push(make_node(AST_Assign, self, { | |
| operator: "=", | |
| logical: false, | |
| left: sym, | |
| right: value.clone() | |
| })); | |
| } | |
| function flatten_args(decls, expressions) { | |
| var len = fn.argnames.length; | |
| for (var i = self.args.length; --i >= len;) { | |
| expressions.push(self.args[i]); | |
| } | |
| for (i = len; --i >= 0;) { | |
| var name = fn.argnames[i]; | |
| var value = self.args[i]; | |
| if (has_flag(name, UNUSED) || !name.name || scope.conflicting_def(name.name)) { | |
| if (value) expressions.push(value); | |
| } else { | |
| var symbol = make_node(AST_SymbolVar, name, name); | |
| name.definition().orig.push(symbol); | |
| if (!value && in_loop) value = make_node(AST_Undefined, self); | |
| append_var(decls, expressions, symbol, value); | |
| } | |
| } | |
| decls.reverse(); | |
| expressions.reverse(); | |
| } | |
| function flatten_vars(decls, expressions) { | |
| var pos = expressions.length; | |
| for (var i = 0, lines = fn.body.length; i < lines; i++) { | |
| var stat = fn.body[i]; | |
| if (!(stat instanceof AST_Var)) continue; | |
| for (var j = 0, defs = stat.definitions.length; j < defs; j++) { | |
| var var_def = stat.definitions[j]; | |
| var name = var_def.name; | |
| append_var(decls, expressions, name, var_def.value); | |
| if (in_loop && fn.argnames.every((argname) => | |
| argname.name != name.name | |
| )) { | |
| var def = fn.variables.get(name.name); | |
| var sym = make_node(AST_SymbolRef, name, name); | |
| def.references.push(sym); | |
| expressions.splice(pos++, 0, make_node(AST_Assign, var_def, { | |
| operator: "=", | |
| logical: false, | |
| left: sym, | |
| right: make_node(AST_Undefined, name) | |
| })); | |
| } | |
| } | |
| } | |
| } | |
| function flatten_fn(returned_value) { | |
| var decls = []; | |
| var expressions = []; | |
| flatten_args(decls, expressions); | |
| flatten_vars(decls, expressions); | |
| expressions.push(returned_value); | |
| if (decls.length) { | |
| const i = scope.body.indexOf(compressor.parent(level - 1)) + 1; | |
| scope.body.splice(i, 0, make_node(AST_Var, fn, { | |
| definitions: decls | |
| })); | |
| } | |
| return expressions.map(exp => exp.clone(true)); | |
| } | |
| } | |
Xet Storage Details
- Size:
- 23.2 kB
- Xet hash:
- 7081ab2541061797de56683475f4d5604df048959ad38017003c36db1fc21e2d
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.