diff options
Diffstat (limited to 'node_modules/terser/lib/compress/inference.js')
| -rw-r--r-- | node_modules/terser/lib/compress/inference.js | 928 |
1 files changed, 928 insertions, 0 deletions
diff --git a/node_modules/terser/lib/compress/inference.js b/node_modules/terser/lib/compress/inference.js new file mode 100644 index 00000000..2068274e --- /dev/null +++ b/node_modules/terser/lib/compress/inference.js @@ -0,0 +1,928 @@ +/*********************************************************************** + + 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_Arrow, + AST_Assign, + AST_BigInt, + AST_Binary, + AST_Block, + AST_BlockStatement, + AST_Call, + AST_Case, + AST_Chain, + AST_Class, + AST_DefClass, + AST_ClassStaticBlock, + AST_ClassProperty, + AST_ConciseMethod, + AST_Conditional, + AST_Constant, + AST_Definitions, + AST_Dot, + AST_EmptyStatement, + AST_Expansion, + AST_False, + AST_ForIn, + AST_Function, + AST_If, + AST_Import, + AST_ImportMeta, + AST_Jump, + AST_LabeledStatement, + AST_Lambda, + AST_New, + AST_Node, + AST_Null, + AST_Number, + AST_Object, + AST_ObjectGetter, + AST_ObjectKeyVal, + AST_ObjectProperty, + AST_ObjectSetter, + AST_PropAccess, + AST_RegExp, + AST_Return, + AST_Scope, + AST_Sequence, + AST_SimpleStatement, + AST_Statement, + AST_String, + AST_Sub, + AST_Switch, + AST_SwitchBranch, + AST_SymbolClassProperty, + AST_SymbolDeclaration, + AST_SymbolRef, + AST_TemplateSegment, + AST_TemplateString, + AST_This, + AST_True, + AST_Try, + AST_Unary, + AST_UnaryPostfix, + AST_UnaryPrefix, + AST_Undefined, + AST_VarDef, + + walk, + walk_abort, + + _PURE +} from "../ast.js"; +import { + makePredicate, + return_true, + return_false, + return_null, + return_this, + make_node, + member, + has_annotation, +} from "../utils/index.js"; +import { make_sequence, best_of_expression, read_property } from "./common.js"; + +import { INLINED, UNDEFINED, has_flag } from "./compressor-flags.js"; +import { pure_prop_access_globals, is_pure_native_fn, is_pure_native_method } from "./native-objects.js"; + +// Functions and methods to infer certain facts about expressions +// It's not always possible to be 100% sure about something just by static analysis, +// so `true` means yes, and `false` means maybe + +export const is_undeclared_ref = (node) => + node instanceof AST_SymbolRef && node.definition().undeclared; + +export const lazy_op = makePredicate("&& || ??"); +export const unary_side_effects = makePredicate("delete ++ --"); + +// methods to determine whether an expression has a boolean result type +(function(def_is_boolean) { + const unary_bool = makePredicate("! delete"); + const binary_bool = makePredicate("in instanceof == != === !== < <= >= >"); + def_is_boolean(AST_Node, return_false); + def_is_boolean(AST_UnaryPrefix, function() { + return unary_bool.has(this.operator); + }); + def_is_boolean(AST_Binary, function() { + return binary_bool.has(this.operator) + || lazy_op.has(this.operator) + && this.left.is_boolean() + && this.right.is_boolean(); + }); + def_is_boolean(AST_Conditional, function() { + return this.consequent.is_boolean() && this.alternative.is_boolean(); + }); + def_is_boolean(AST_Assign, function() { + return this.operator == "=" && this.right.is_boolean(); + }); + def_is_boolean(AST_Sequence, function() { + return this.tail_node().is_boolean(); + }); + def_is_boolean(AST_True, return_true); + def_is_boolean(AST_False, return_true); +})(function(node, func) { + node.DEFMETHOD("is_boolean", func); +}); + +// methods to determine if an expression has a numeric result type +(function(def_is_number) { + def_is_number(AST_Node, return_false); + def_is_number(AST_Number, return_true); + const unary = makePredicate("+ - ~ ++ --"); + def_is_number(AST_Unary, function() { + return unary.has(this.operator) && !(this.expression instanceof AST_BigInt); + }); + const numeric_ops = makePredicate("- * / % & | ^ << >> >>>"); + def_is_number(AST_Binary, function(compressor) { + return numeric_ops.has(this.operator) || this.operator == "+" + && this.left.is_number(compressor) + && this.right.is_number(compressor); + }); + def_is_number(AST_Assign, function(compressor) { + return numeric_ops.has(this.operator.slice(0, -1)) + || this.operator == "=" && this.right.is_number(compressor); + }); + def_is_number(AST_Sequence, function(compressor) { + return this.tail_node().is_number(compressor); + }); + def_is_number(AST_Conditional, function(compressor) { + return this.consequent.is_number(compressor) && this.alternative.is_number(compressor); + }); +})(function(node, func) { + node.DEFMETHOD("is_number", func); +}); + +// methods to determine if an expression has a string result type +(function(def_is_string) { + def_is_string(AST_Node, return_false); + def_is_string(AST_String, return_true); + def_is_string(AST_TemplateString, return_true); + def_is_string(AST_UnaryPrefix, function() { + return this.operator == "typeof"; + }); + def_is_string(AST_Binary, function(compressor) { + return this.operator == "+" && + (this.left.is_string(compressor) || this.right.is_string(compressor)); + }); + def_is_string(AST_Assign, function(compressor) { + return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor); + }); + def_is_string(AST_Sequence, function(compressor) { + return this.tail_node().is_string(compressor); + }); + def_is_string(AST_Conditional, function(compressor) { + return this.consequent.is_string(compressor) && this.alternative.is_string(compressor); + }); +})(function(node, func) { + node.DEFMETHOD("is_string", func); +}); + +export function is_undefined(node, compressor) { + return ( + has_flag(node, UNDEFINED) + || node instanceof AST_Undefined + || node instanceof AST_UnaryPrefix + && node.operator == "void" + && !node.expression.has_side_effects(compressor) + ); +} + +// Is the node explicitly null or undefined. +function is_null_or_undefined(node, compressor) { + let fixed; + return ( + node instanceof AST_Null + || is_undefined(node, compressor) + || ( + node instanceof AST_SymbolRef + && (fixed = node.definition().fixed) instanceof AST_Node + && is_nullish(fixed, compressor) + ) + ); +} + +// Find out if this expression is optionally chained from a base-point that we +// can statically analyze as null or undefined. +export function is_nullish_shortcircuited(node, compressor) { + if (node instanceof AST_PropAccess || node instanceof AST_Call) { + return ( + (node.optional && is_null_or_undefined(node.expression, compressor)) + || is_nullish_shortcircuited(node.expression, compressor) + ); + } + if (node instanceof AST_Chain) return is_nullish_shortcircuited(node.expression, compressor); + return false; +} + +// Find out if something is == null, or can short circuit into nullish. +// Used to optimize ?. and ?? +export function is_nullish(node, compressor) { + if (is_null_or_undefined(node, compressor)) return true; + return is_nullish_shortcircuited(node, compressor); +} + +// Determine if expression might cause side effects +// If there's a possibility that a node may change something when it's executed, this returns true +(function(def_has_side_effects) { + def_has_side_effects(AST_Node, return_true); + + def_has_side_effects(AST_EmptyStatement, return_false); + def_has_side_effects(AST_Constant, return_false); + def_has_side_effects(AST_This, return_false); + + function any(list, compressor) { + for (var i = list.length; --i >= 0;) + if (list[i].has_side_effects(compressor)) + return true; + return false; + } + + def_has_side_effects(AST_Block, function(compressor) { + return any(this.body, compressor); + }); + def_has_side_effects(AST_Call, function(compressor) { + if ( + !this.is_callee_pure(compressor) + && (!this.expression.is_call_pure(compressor) + || this.expression.has_side_effects(compressor)) + ) { + return true; + } + return any(this.args, compressor); + }); + def_has_side_effects(AST_Switch, function(compressor) { + return this.expression.has_side_effects(compressor) + || any(this.body, compressor); + }); + def_has_side_effects(AST_Case, function(compressor) { + return this.expression.has_side_effects(compressor) + || any(this.body, compressor); + }); + def_has_side_effects(AST_Try, function(compressor) { + return this.body.has_side_effects(compressor) + || this.bcatch && this.bcatch.has_side_effects(compressor) + || this.bfinally && this.bfinally.has_side_effects(compressor); + }); + def_has_side_effects(AST_If, function(compressor) { + return this.condition.has_side_effects(compressor) + || this.body && this.body.has_side_effects(compressor) + || this.alternative && this.alternative.has_side_effects(compressor); + }); + def_has_side_effects(AST_ImportMeta, return_false); + def_has_side_effects(AST_LabeledStatement, function(compressor) { + return this.body.has_side_effects(compressor); + }); + def_has_side_effects(AST_SimpleStatement, function(compressor) { + return this.body.has_side_effects(compressor); + }); + def_has_side_effects(AST_Lambda, return_false); + def_has_side_effects(AST_Class, function (compressor) { + if (this.extends && this.extends.has_side_effects(compressor)) { + return true; + } + return any(this.properties, compressor); + }); + def_has_side_effects(AST_ClassStaticBlock, function(compressor) { + return any(this.body, compressor); + }); + def_has_side_effects(AST_Binary, function(compressor) { + return this.left.has_side_effects(compressor) + || this.right.has_side_effects(compressor); + }); + def_has_side_effects(AST_Assign, return_true); + def_has_side_effects(AST_Conditional, function(compressor) { + return this.condition.has_side_effects(compressor) + || this.consequent.has_side_effects(compressor) + || this.alternative.has_side_effects(compressor); + }); + def_has_side_effects(AST_Unary, function(compressor) { + return unary_side_effects.has(this.operator) + || this.expression.has_side_effects(compressor); + }); + def_has_side_effects(AST_SymbolRef, function(compressor) { + return !this.is_declared(compressor) && !pure_prop_access_globals.has(this.name); + }); + def_has_side_effects(AST_SymbolClassProperty, return_false); + def_has_side_effects(AST_SymbolDeclaration, return_false); + def_has_side_effects(AST_Object, function(compressor) { + return any(this.properties, compressor); + }); + def_has_side_effects(AST_ObjectProperty, function(compressor) { + return ( + this.computed_key() && this.key.has_side_effects(compressor) + || this.value && this.value.has_side_effects(compressor) + ); + }); + def_has_side_effects(AST_ClassProperty, function(compressor) { + return ( + this.computed_key() && this.key.has_side_effects(compressor) + || this.static && this.value && this.value.has_side_effects(compressor) + ); + }); + def_has_side_effects(AST_ConciseMethod, function(compressor) { + return this.computed_key() && this.key.has_side_effects(compressor); + }); + def_has_side_effects(AST_ObjectGetter, function(compressor) { + return this.computed_key() && this.key.has_side_effects(compressor); + }); + def_has_side_effects(AST_ObjectSetter, function(compressor) { + return this.computed_key() && this.key.has_side_effects(compressor); + }); + def_has_side_effects(AST_Array, function(compressor) { + return any(this.elements, compressor); + }); + def_has_side_effects(AST_Dot, function(compressor) { + if (is_nullish(this, compressor)) { + return this.expression.has_side_effects(compressor); + } + if (!this.optional && this.expression.may_throw_on_access(compressor)) { + return true; + } + + return this.expression.has_side_effects(compressor); + }); + def_has_side_effects(AST_Sub, function(compressor) { + if (is_nullish(this, compressor)) { + return this.expression.has_side_effects(compressor); + } + if (!this.optional && this.expression.may_throw_on_access(compressor)) { + return true; + } + + var property = this.property.has_side_effects(compressor); + if (property && this.optional) return true; // "?." is a condition + + return property || this.expression.has_side_effects(compressor); + }); + def_has_side_effects(AST_Chain, function (compressor) { + return this.expression.has_side_effects(compressor); + }); + def_has_side_effects(AST_Sequence, function(compressor) { + return any(this.expressions, compressor); + }); + def_has_side_effects(AST_Definitions, function(compressor) { + return any(this.definitions, compressor); + }); + def_has_side_effects(AST_VarDef, function() { + return this.value; + }); + def_has_side_effects(AST_TemplateSegment, return_false); + def_has_side_effects(AST_TemplateString, function(compressor) { + return any(this.segments, compressor); + }); +})(function(node, func) { + node.DEFMETHOD("has_side_effects", func); +}); + +// determine if expression may throw +(function(def_may_throw) { + def_may_throw(AST_Node, return_true); + + def_may_throw(AST_Constant, return_false); + def_may_throw(AST_EmptyStatement, return_false); + def_may_throw(AST_Lambda, return_false); + def_may_throw(AST_SymbolDeclaration, return_false); + def_may_throw(AST_This, return_false); + def_may_throw(AST_ImportMeta, return_false); + + function any(list, compressor) { + for (var i = list.length; --i >= 0;) + if (list[i].may_throw(compressor)) + return true; + return false; + } + + def_may_throw(AST_Class, function(compressor) { + if (this.extends && this.extends.may_throw(compressor)) return true; + return any(this.properties, compressor); + }); + def_may_throw(AST_ClassStaticBlock, function (compressor) { + return any(this.body, compressor); + }); + + def_may_throw(AST_Array, function(compressor) { + return any(this.elements, compressor); + }); + def_may_throw(AST_Assign, function(compressor) { + if (this.right.may_throw(compressor)) return true; + if (!compressor.has_directive("use strict") + && this.operator == "=" + && this.left instanceof AST_SymbolRef) { + return false; + } + return this.left.may_throw(compressor); + }); + def_may_throw(AST_Binary, function(compressor) { + return this.left.may_throw(compressor) + || this.right.may_throw(compressor); + }); + def_may_throw(AST_Block, function(compressor) { + return any(this.body, compressor); + }); + def_may_throw(AST_Call, function(compressor) { + if (is_nullish(this, compressor)) return false; + if (any(this.args, compressor)) return true; + if (this.is_callee_pure(compressor)) return false; + if (this.expression.may_throw(compressor)) return true; + return !(this.expression instanceof AST_Lambda) + || any(this.expression.body, compressor); + }); + def_may_throw(AST_Case, function(compressor) { + return this.expression.may_throw(compressor) + || any(this.body, compressor); + }); + def_may_throw(AST_Conditional, function(compressor) { + return this.condition.may_throw(compressor) + || this.consequent.may_throw(compressor) + || this.alternative.may_throw(compressor); + }); + def_may_throw(AST_Definitions, function(compressor) { + return any(this.definitions, compressor); + }); + def_may_throw(AST_If, function(compressor) { + return this.condition.may_throw(compressor) + || this.body && this.body.may_throw(compressor) + || this.alternative && this.alternative.may_throw(compressor); + }); + def_may_throw(AST_LabeledStatement, function(compressor) { + return this.body.may_throw(compressor); + }); + def_may_throw(AST_Object, function(compressor) { + return any(this.properties, compressor); + }); + def_may_throw(AST_ObjectProperty, function(compressor) { + // TODO key may throw too + return this.value ? this.value.may_throw(compressor) : false; + }); + def_may_throw(AST_ClassProperty, function(compressor) { + return ( + this.computed_key() && this.key.may_throw(compressor) + || this.static && this.value && this.value.may_throw(compressor) + ); + }); + def_may_throw(AST_ConciseMethod, function(compressor) { + return this.computed_key() && this.key.may_throw(compressor); + }); + def_may_throw(AST_ObjectGetter, function(compressor) { + return this.computed_key() && this.key.may_throw(compressor); + }); + def_may_throw(AST_ObjectSetter, function(compressor) { + return this.computed_key() && this.key.may_throw(compressor); + }); + def_may_throw(AST_Return, function(compressor) { + return this.value && this.value.may_throw(compressor); + }); + def_may_throw(AST_Sequence, function(compressor) { + return any(this.expressions, compressor); + }); + def_may_throw(AST_SimpleStatement, function(compressor) { + return this.body.may_throw(compressor); + }); + def_may_throw(AST_Dot, function(compressor) { + if (is_nullish(this, compressor)) return false; + return !this.optional && this.expression.may_throw_on_access(compressor) + || this.expression.may_throw(compressor); + }); + def_may_throw(AST_Sub, function(compressor) { + if (is_nullish(this, compressor)) return false; + return !this.optional && this.expression.may_throw_on_access(compressor) + || this.expression.may_throw(compressor) + || this.property.may_throw(compressor); + }); + def_may_throw(AST_Chain, function(compressor) { + return this.expression.may_throw(compressor); + }); + def_may_throw(AST_Switch, function(compressor) { + return this.expression.may_throw(compressor) + || any(this.body, compressor); + }); + def_may_throw(AST_SymbolRef, function(compressor) { + return !this.is_declared(compressor) && !pure_prop_access_globals.has(this.name); + }); + def_may_throw(AST_SymbolClassProperty, return_false); + def_may_throw(AST_Try, function(compressor) { + return this.bcatch ? this.bcatch.may_throw(compressor) : this.body.may_throw(compressor) + || this.bfinally && this.bfinally.may_throw(compressor); + }); + def_may_throw(AST_Unary, function(compressor) { + if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef) + return false; + return this.expression.may_throw(compressor); + }); + def_may_throw(AST_VarDef, function(compressor) { + if (!this.value) return false; + return this.value.may_throw(compressor); + }); +})(function(node, func) { + node.DEFMETHOD("may_throw", func); +}); + +// determine if expression is constant +(function(def_is_constant_expression) { + function all_refs_local(scope) { + let result = true; + walk(this, node => { + if (node instanceof AST_SymbolRef) { + if (has_flag(this, INLINED)) { + result = false; + return walk_abort; + } + var def = node.definition(); + if ( + member(def, this.enclosed) + && !this.variables.has(def.name) + ) { + if (scope) { + var scope_def = scope.find_variable(node); + if (def.undeclared ? !scope_def : scope_def === def) { + result = "f"; + return true; + } + } + result = false; + return walk_abort; + } + return true; + } + if (node instanceof AST_This && this instanceof AST_Arrow) { + result = false; + return walk_abort; + } + }); + return result; + } + + def_is_constant_expression(AST_Node, return_false); + def_is_constant_expression(AST_Constant, return_true); + def_is_constant_expression(AST_Class, function(scope) { + if (this.extends && !this.extends.is_constant_expression(scope)) { + return false; + } + + for (const prop of this.properties) { + if (prop.computed_key() && !prop.key.is_constant_expression(scope)) { + return false; + } + if (prop.static && prop.value && !prop.value.is_constant_expression(scope)) { + return false; + } + if (prop instanceof AST_ClassStaticBlock) { + return false; + } + } + + return all_refs_local.call(this, scope); + }); + def_is_constant_expression(AST_Lambda, all_refs_local); + def_is_constant_expression(AST_Unary, function() { + return this.expression.is_constant_expression(); + }); + def_is_constant_expression(AST_Binary, function() { + return this.left.is_constant_expression() + && this.right.is_constant_expression(); + }); + def_is_constant_expression(AST_Array, function() { + return this.elements.every((l) => l.is_constant_expression()); + }); + def_is_constant_expression(AST_Object, function() { + return this.properties.every((l) => l.is_constant_expression()); + }); + def_is_constant_expression(AST_ObjectProperty, function() { + return !!(!(this.key instanceof AST_Node) && this.value && this.value.is_constant_expression()); + }); +})(function(node, func) { + node.DEFMETHOD("is_constant_expression", func); +}); + + +// may_throw_on_access() +// returns true if this node may be null, undefined or contain `AST_Accessor` +(function(def_may_throw_on_access) { + AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) { + return !compressor.option("pure_getters") + || this._dot_throw(compressor); + }); + + function is_strict(compressor) { + return /strict/.test(compressor.option("pure_getters")); + } + + def_may_throw_on_access(AST_Node, is_strict); + def_may_throw_on_access(AST_Null, return_true); + def_may_throw_on_access(AST_Undefined, return_true); + def_may_throw_on_access(AST_Constant, return_false); + def_may_throw_on_access(AST_Array, return_false); + def_may_throw_on_access(AST_Object, function(compressor) { + if (!is_strict(compressor)) return false; + for (var i = this.properties.length; --i >=0;) + if (this.properties[i]._dot_throw(compressor)) return true; + return false; + }); + // Do not be as strict with classes as we are with objects. + // Hopefully the community is not going to abuse static getters and setters. + // https://github.com/terser/terser/issues/724#issuecomment-643655656 + def_may_throw_on_access(AST_Class, return_false); + def_may_throw_on_access(AST_ObjectProperty, return_false); + def_may_throw_on_access(AST_ObjectGetter, return_true); + def_may_throw_on_access(AST_Expansion, function(compressor) { + return this.expression._dot_throw(compressor); + }); + def_may_throw_on_access(AST_Function, return_false); + def_may_throw_on_access(AST_Arrow, return_false); + def_may_throw_on_access(AST_UnaryPostfix, return_false); + def_may_throw_on_access(AST_UnaryPrefix, function() { + return this.operator == "void"; + }); + def_may_throw_on_access(AST_Binary, function(compressor) { + return (this.operator == "&&" || this.operator == "||" || this.operator == "??") + && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor)); + }); + def_may_throw_on_access(AST_Assign, function(compressor) { + if (this.logical) return true; + + return this.operator == "=" + && this.right._dot_throw(compressor); + }); + def_may_throw_on_access(AST_Conditional, function(compressor) { + return this.consequent._dot_throw(compressor) + || this.alternative._dot_throw(compressor); + }); + def_may_throw_on_access(AST_Dot, function(compressor) { + if (!is_strict(compressor)) return false; + + if (this.property == "prototype") { + return !( + this.expression instanceof AST_Function + || this.expression instanceof AST_Class + ); + } + return true; + }); + def_may_throw_on_access(AST_Chain, function(compressor) { + return this.expression._dot_throw(compressor); + }); + def_may_throw_on_access(AST_Sequence, function(compressor) { + return this.tail_node()._dot_throw(compressor); + }); + def_may_throw_on_access(AST_SymbolRef, function(compressor) { + if (this.name === "arguments" && this.scope instanceof AST_Lambda) return false; + if (has_flag(this, UNDEFINED)) return true; + if (!is_strict(compressor)) return false; + if (is_undeclared_ref(this) && this.is_declared(compressor)) return false; + if (this.is_immutable()) return false; + var fixed = this.fixed_value(); + return !fixed || fixed._dot_throw(compressor); + }); +})(function(node, func) { + node.DEFMETHOD("_dot_throw", func); +}); + +export function is_lhs(node, parent) { + if (parent instanceof AST_Unary && unary_side_effects.has(parent.operator)) return parent.expression; + if (parent instanceof AST_Assign && parent.left === node) return node; + if (parent instanceof AST_ForIn && parent.init === node) return node; +} + +// method to negate an expression +(function(def_negate) { + function basic_negation(exp) { + return make_node(AST_UnaryPrefix, exp, { + operator: "!", + expression: exp + }); + } + function best(orig, alt, first_in_statement) { + var negated = basic_negation(orig); + if (first_in_statement) { + var stat = make_node(AST_SimpleStatement, alt, { + body: alt + }); + return best_of_expression(negated, stat) === stat ? alt : negated; + } + return best_of_expression(negated, alt); + } + def_negate(AST_Node, function() { + return basic_negation(this); + }); + def_negate(AST_Statement, function() { + throw new Error("Cannot negate a statement"); + }); + def_negate(AST_Function, function() { + return basic_negation(this); + }); + def_negate(AST_Class, function() { + return basic_negation(this); + }); + def_negate(AST_Arrow, function() { + return basic_negation(this); + }); + def_negate(AST_UnaryPrefix, function() { + if (this.operator == "!") + return this.expression; + return basic_negation(this); + }); + def_negate(AST_Sequence, function(compressor) { + var expressions = this.expressions.slice(); + expressions.push(expressions.pop().negate(compressor)); + return make_sequence(this, expressions); + }); + def_negate(AST_Conditional, function(compressor, first_in_statement) { + var self = this.clone(); + self.consequent = self.consequent.negate(compressor); + self.alternative = self.alternative.negate(compressor); + return best(this, self, first_in_statement); + }); + def_negate(AST_Binary, function(compressor, first_in_statement) { + var self = this.clone(), op = this.operator; + if (compressor.option("unsafe_comps")) { + switch (op) { + case "<=" : self.operator = ">" ; return self; + case "<" : self.operator = ">=" ; return self; + case ">=" : self.operator = "<" ; return self; + case ">" : self.operator = "<=" ; return self; + } + } + switch (op) { + case "==" : self.operator = "!="; return self; + case "!=" : self.operator = "=="; return self; + case "===": self.operator = "!=="; return self; + case "!==": self.operator = "==="; return self; + case "&&": + self.operator = "||"; + self.left = self.left.negate(compressor, first_in_statement); + self.right = self.right.negate(compressor); + return best(this, self, first_in_statement); + case "||": + self.operator = "&&"; + self.left = self.left.negate(compressor, first_in_statement); + self.right = self.right.negate(compressor); + return best(this, self, first_in_statement); + } + return basic_negation(this); + }); +})(function(node, func) { + node.DEFMETHOD("negate", function(compressor, first_in_statement) { + return func.call(this, compressor, first_in_statement); + }); +}); + +// Is the callee of this function pure? +var global_pure_fns = makePredicate("Boolean decodeURI decodeURIComponent Date encodeURI encodeURIComponent Error escape EvalError isFinite isNaN Number Object parseFloat parseInt RangeError ReferenceError String SyntaxError TypeError unescape URIError"); +AST_Call.DEFMETHOD("is_callee_pure", function(compressor) { + if (compressor.option("unsafe")) { + var expr = this.expression; + var first_arg = (this.args && this.args[0] && this.args[0].evaluate(compressor)); + if ( + expr.expression && expr.expression.name === "hasOwnProperty" && + (first_arg == null || first_arg.thedef && first_arg.thedef.undeclared) + ) { + return false; + } + if (is_undeclared_ref(expr) && global_pure_fns.has(expr.name)) return true; + if ( + expr instanceof AST_Dot + && is_undeclared_ref(expr.expression) + && is_pure_native_fn(expr.expression.name, expr.property) + ) { + return true; + } + } + return !!has_annotation(this, _PURE) || !compressor.pure_funcs(this); +}); + +// If I call this, is it a pure function? +AST_Node.DEFMETHOD("is_call_pure", return_false); +AST_Dot.DEFMETHOD("is_call_pure", function(compressor) { + if (!compressor.option("unsafe")) return; + const expr = this.expression; + + let native_obj; + if (expr instanceof AST_Array) { + native_obj = "Array"; + } else if (expr.is_boolean()) { + native_obj = "Boolean"; + } else if (expr.is_number(compressor)) { + native_obj = "Number"; + } else if (expr instanceof AST_RegExp) { + native_obj = "RegExp"; + } else if (expr.is_string(compressor)) { + native_obj = "String"; + } else if (!this.may_throw_on_access(compressor)) { + native_obj = "Object"; + } + return native_obj != null && is_pure_native_method(native_obj, this.property); +}); + +// tell me if a statement aborts +export const aborts = (thing) => thing && thing.aborts(); + +(function(def_aborts) { + def_aborts(AST_Statement, return_null); + def_aborts(AST_Jump, return_this); + function block_aborts() { + for (var i = 0; i < this.body.length; i++) { + if (aborts(this.body[i])) { + return this.body[i]; + } + } + return null; + } + def_aborts(AST_Import, return_null); + def_aborts(AST_BlockStatement, block_aborts); + def_aborts(AST_SwitchBranch, block_aborts); + def_aborts(AST_DefClass, function () { + for (const prop of this.properties) { + if (prop instanceof AST_ClassStaticBlock) { + if (prop.aborts()) return prop; + } + } + return null; + }); + def_aborts(AST_ClassStaticBlock, block_aborts); + def_aborts(AST_If, function() { + return this.alternative && aborts(this.body) && aborts(this.alternative) && this; + }); +})(function(node, func) { + node.DEFMETHOD("aborts", func); +}); + +AST_Node.DEFMETHOD("contains_this", function() { + return walk(this, node => { + if (node instanceof AST_This) return walk_abort; + if ( + node !== this + && node instanceof AST_Scope + && !(node instanceof AST_Arrow) + ) { + return true; + } + }); +}); + +export function is_modified(compressor, tw, node, value, level, immutable) { + var parent = tw.parent(level); + var lhs = is_lhs(node, parent); + if (lhs) return lhs; + if (!immutable + && parent instanceof AST_Call + && parent.expression === node + && !(value instanceof AST_Arrow) + && !(value instanceof AST_Class) + && !parent.is_callee_pure(compressor) + && (!(value instanceof AST_Function) + || !(parent instanceof AST_New) && value.contains_this())) { + return true; + } + if (parent instanceof AST_Array) { + return is_modified(compressor, tw, parent, parent, level + 1); + } + if (parent instanceof AST_ObjectKeyVal && node === parent.value) { + var obj = tw.parent(level + 1); + return is_modified(compressor, tw, obj, obj, level + 2); + } + if (parent instanceof AST_PropAccess && parent.expression === node) { + var prop = read_property(value, parent.property); + return !immutable && is_modified(compressor, tw, parent, prop, level + 1); + } +} |
