1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006 |
- /***********************************************************************
- 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_Toplevel,
- AST_True,
- AST_Try,
- AST_Unary,
- AST_UnaryPostfix,
- AST_UnaryPrefix,
- AST_Undefined,
- AST_VarDef,
- TreeTransformer,
- walk,
- walk_abort,
- _PURE
- } from "../ast.js";
- import {
- makePredicate,
- return_true,
- return_false,
- return_null,
- return_this,
- make_node,
- member,
- noop,
- has_annotation,
- HOP
- } from "../utils/index.js";
- import { make_node_from_constant, 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;
- }
- (function(def_find_defs) {
- function to_node(value, orig) {
- if (value instanceof AST_Node) {
- if (!(value instanceof AST_Constant)) {
- // Value may be a function, an array including functions and even a complex assign / block expression,
- // so it should never be shared in different places.
- // Otherwise wrong information may be used in the compression phase
- value = value.clone(true);
- }
- return make_node(value.CTOR, orig, value);
- }
- if (Array.isArray(value)) return make_node(AST_Array, orig, {
- elements: value.map(function(value) {
- return to_node(value, orig);
- })
- });
- if (value && typeof value == "object") {
- var props = [];
- for (var key in value) if (HOP(value, key)) {
- props.push(make_node(AST_ObjectKeyVal, orig, {
- key: key,
- value: to_node(value[key], orig)
- }));
- }
- return make_node(AST_Object, orig, {
- properties: props
- });
- }
- return make_node_from_constant(value, orig);
- }
- AST_Toplevel.DEFMETHOD("resolve_defines", function(compressor) {
- if (!compressor.option("global_defs")) return this;
- this.figure_out_scope({ ie8: compressor.option("ie8") });
- return this.transform(new TreeTransformer(function(node) {
- var def = node._find_defs(compressor, "");
- if (!def) return;
- var level = 0, child = node, parent;
- while (parent = this.parent(level++)) {
- if (!(parent instanceof AST_PropAccess)) break;
- if (parent.expression !== child) break;
- child = parent;
- }
- if (is_lhs(child, parent)) {
- return;
- }
- return def;
- }));
- });
- def_find_defs(AST_Node, noop);
- def_find_defs(AST_Chain, function(compressor, suffix) {
- return this.expression._find_defs(compressor, suffix);
- });
- def_find_defs(AST_Dot, function(compressor, suffix) {
- return this.expression._find_defs(compressor, "." + this.property + suffix);
- });
- def_find_defs(AST_SymbolDeclaration, function() {
- if (!this.global()) return;
- });
- def_find_defs(AST_SymbolRef, function(compressor, suffix) {
- if (!this.global()) return;
- var defines = compressor.option("global_defs");
- var name = this.name + suffix;
- if (HOP(defines, name)) return to_node(defines[name], this);
- });
- def_find_defs(AST_ImportMeta, function(compressor, suffix) {
- var defines = compressor.option("global_defs");
- var name = "import.meta" + suffix;
- if (HOP(defines, name)) return to_node(defines[name], this);
- });
- })(function(node, func) {
- node.DEFMETHOD("_find_defs", func);
- });
- // 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);
- }
- }
|