You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							470 lines
						
					
					
						
							15 KiB
						
					
					
				
			
		
		
	
	
							470 lines
						
					
					
						
							15 KiB
						
					
					
				/***********************************************************************
 | 
						|
 | 
						|
  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 {
 | 
						|
    HOP,
 | 
						|
    makePredicate,
 | 
						|
    return_this,
 | 
						|
    string_template,
 | 
						|
    regexp_source_fix,
 | 
						|
    regexp_is_safe,
 | 
						|
} from "../utils/index.js";
 | 
						|
import {
 | 
						|
    AST_Array,
 | 
						|
    AST_BigInt,
 | 
						|
    AST_Binary,
 | 
						|
    AST_Call,
 | 
						|
    AST_Chain,
 | 
						|
    AST_Class,
 | 
						|
    AST_Conditional,
 | 
						|
    AST_Constant,
 | 
						|
    AST_Dot,
 | 
						|
    AST_Expansion,
 | 
						|
    AST_Function,
 | 
						|
    AST_Lambda,
 | 
						|
    AST_New,
 | 
						|
    AST_Node,
 | 
						|
    AST_Object,
 | 
						|
    AST_PropAccess,
 | 
						|
    AST_RegExp,
 | 
						|
    AST_Statement,
 | 
						|
    AST_Symbol,
 | 
						|
    AST_SymbolRef,
 | 
						|
    AST_TemplateString,
 | 
						|
    AST_UnaryPrefix,
 | 
						|
    AST_With,
 | 
						|
} from "../ast.js";
 | 
						|
import { is_undeclared_ref} from "./inference.js";
 | 
						|
import { is_pure_native_value, is_pure_native_fn, is_pure_native_method } from "./native-objects.js";
 | 
						|
 | 
						|
// methods to evaluate a constant expression
 | 
						|
 | 
						|
function def_eval(node, func) {
 | 
						|
    node.DEFMETHOD("_eval", func);
 | 
						|
}
 | 
						|
 | 
						|
// Used to propagate a nullish short-circuit signal upwards through the chain.
 | 
						|
export const nullish = Symbol("This AST_Chain is nullish");
 | 
						|
 | 
						|
// If the node has been successfully reduced to a constant,
 | 
						|
// then its value is returned; otherwise the element itself
 | 
						|
// is returned.
 | 
						|
// They can be distinguished as constant value is never a
 | 
						|
// descendant of AST_Node.
 | 
						|
AST_Node.DEFMETHOD("evaluate", function (compressor) {
 | 
						|
    if (!compressor.option("evaluate"))
 | 
						|
        return this;
 | 
						|
    var val = this._eval(compressor, 1);
 | 
						|
    if (!val || val instanceof RegExp)
 | 
						|
        return val;
 | 
						|
    if (typeof val == "function" || typeof val == "object" || val == nullish)
 | 
						|
        return this;
 | 
						|
 | 
						|
    // Evaluated strings can be larger than the original expression
 | 
						|
    if (typeof val === "string") {
 | 
						|
        const unevaluated_size = this.size(compressor);
 | 
						|
        if (val.length + 2 > unevaluated_size) return this;
 | 
						|
    }
 | 
						|
 | 
						|
    return val;
 | 
						|
});
 | 
						|
 | 
						|
var unaryPrefix = makePredicate("! ~ - + void");
 | 
						|
AST_Node.DEFMETHOD("is_constant", function () {
 | 
						|
    // Accomodate when compress option evaluate=false
 | 
						|
    // as well as the common constant expressions !0 and -1
 | 
						|
    if (this instanceof AST_Constant) {
 | 
						|
        return !(this instanceof AST_RegExp);
 | 
						|
    } else {
 | 
						|
        return this instanceof AST_UnaryPrefix
 | 
						|
            && this.expression instanceof AST_Constant
 | 
						|
            && unaryPrefix.has(this.operator);
 | 
						|
    }
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_Statement, function () {
 | 
						|
    throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_Lambda, return_this);
 | 
						|
def_eval(AST_Class, return_this);
 | 
						|
def_eval(AST_Node, return_this);
 | 
						|
def_eval(AST_Constant, function () {
 | 
						|
    return this.getValue();
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_BigInt, return_this);
 | 
						|
 | 
						|
def_eval(AST_RegExp, function (compressor) {
 | 
						|
    let evaluated = compressor.evaluated_regexps.get(this.value);
 | 
						|
    if (evaluated === undefined && regexp_is_safe(this.value.source)) {
 | 
						|
        try {
 | 
						|
            const { source, flags } = this.value;
 | 
						|
            evaluated = new RegExp(source, flags);
 | 
						|
        } catch (e) {
 | 
						|
            evaluated = null;
 | 
						|
        }
 | 
						|
        compressor.evaluated_regexps.set(this.value, evaluated);
 | 
						|
    }
 | 
						|
    return evaluated || this;
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_TemplateString, function () {
 | 
						|
    if (this.segments.length !== 1) return this;
 | 
						|
    return this.segments[0].value;
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_Function, function (compressor) {
 | 
						|
    if (compressor.option("unsafe")) {
 | 
						|
        var fn = function () { };
 | 
						|
        fn.node = this;
 | 
						|
        fn.toString = () => this.print_to_string();
 | 
						|
        return fn;
 | 
						|
    }
 | 
						|
    return this;
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_Array, function (compressor, depth) {
 | 
						|
    if (compressor.option("unsafe")) {
 | 
						|
        var elements = [];
 | 
						|
        for (var i = 0, len = this.elements.length; i < len; i++) {
 | 
						|
            var element = this.elements[i];
 | 
						|
            var value = element._eval(compressor, depth);
 | 
						|
            if (element === value)
 | 
						|
                return this;
 | 
						|
            elements.push(value);
 | 
						|
        }
 | 
						|
        return elements;
 | 
						|
    }
 | 
						|
    return this;
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_Object, function (compressor, depth) {
 | 
						|
    if (compressor.option("unsafe")) {
 | 
						|
        var val = {};
 | 
						|
        for (var i = 0, len = this.properties.length; i < len; i++) {
 | 
						|
            var prop = this.properties[i];
 | 
						|
            if (prop instanceof AST_Expansion)
 | 
						|
                return this;
 | 
						|
            var key = prop.key;
 | 
						|
            if (key instanceof AST_Symbol) {
 | 
						|
                key = key.name;
 | 
						|
            } else if (key instanceof AST_Node) {
 | 
						|
                key = key._eval(compressor, depth);
 | 
						|
                if (key === prop.key)
 | 
						|
                    return this;
 | 
						|
            }
 | 
						|
            if (typeof Object.prototype[key] === "function") {
 | 
						|
                return this;
 | 
						|
            }
 | 
						|
            if (prop.value instanceof AST_Function)
 | 
						|
                continue;
 | 
						|
            val[key] = prop.value._eval(compressor, depth);
 | 
						|
            if (val[key] === prop.value)
 | 
						|
                return this;
 | 
						|
        }
 | 
						|
        return val;
 | 
						|
    }
 | 
						|
    return this;
 | 
						|
});
 | 
						|
 | 
						|
var non_converting_unary = makePredicate("! typeof void");
 | 
						|
def_eval(AST_UnaryPrefix, function (compressor, depth) {
 | 
						|
    var e = this.expression;
 | 
						|
    // Function would be evaluated to an array and so typeof would
 | 
						|
    // incorrectly return 'object'. Hence making is a special case.
 | 
						|
    if (compressor.option("typeofs")
 | 
						|
        && this.operator == "typeof"
 | 
						|
        && (e instanceof AST_Lambda
 | 
						|
            || e instanceof AST_SymbolRef
 | 
						|
            && e.fixed_value() instanceof AST_Lambda)) {
 | 
						|
        return typeof function () { };
 | 
						|
    }
 | 
						|
    if (!non_converting_unary.has(this.operator))
 | 
						|
        depth++;
 | 
						|
    e = e._eval(compressor, depth);
 | 
						|
    if (e === this.expression)
 | 
						|
        return this;
 | 
						|
    switch (this.operator) {
 | 
						|
        case "!": return !e;
 | 
						|
        case "typeof":
 | 
						|
            // typeof <RegExp> returns "object" or "function" on different platforms
 | 
						|
            // so cannot evaluate reliably
 | 
						|
            if (e instanceof RegExp)
 | 
						|
                return this;
 | 
						|
            return typeof e;
 | 
						|
        case "void": return void e;
 | 
						|
        case "~": return ~e;
 | 
						|
        case "-": return -e;
 | 
						|
        case "+": return +e;
 | 
						|
    }
 | 
						|
    return this;
 | 
						|
});
 | 
						|
 | 
						|
var non_converting_binary = makePredicate("&& || ?? === !==");
 | 
						|
const identity_comparison = makePredicate("== != === !==");
 | 
						|
const has_identity = value => typeof value === "object"
 | 
						|
    || typeof value === "function"
 | 
						|
    || typeof value === "symbol";
 | 
						|
 | 
						|
def_eval(AST_Binary, function (compressor, depth) {
 | 
						|
    if (!non_converting_binary.has(this.operator))
 | 
						|
        depth++;
 | 
						|
 | 
						|
    var left = this.left._eval(compressor, depth);
 | 
						|
    if (left === this.left)
 | 
						|
        return this;
 | 
						|
    var right = this.right._eval(compressor, depth);
 | 
						|
    if (right === this.right)
 | 
						|
        return this;
 | 
						|
    var result;
 | 
						|
 | 
						|
    if (left != null
 | 
						|
        && right != null
 | 
						|
        && identity_comparison.has(this.operator)
 | 
						|
        && has_identity(left)
 | 
						|
        && has_identity(right)
 | 
						|
        && typeof left === typeof right) {
 | 
						|
        // Do not compare by reference
 | 
						|
        return this;
 | 
						|
    }
 | 
						|
 | 
						|
    switch (this.operator) {
 | 
						|
        case "&&": result = left && right; break;
 | 
						|
        case "||": result = left || right; break;
 | 
						|
        case "??": result = left != null ? left : right; break;
 | 
						|
        case "|": result = left | right; break;
 | 
						|
        case "&": result = left & right; break;
 | 
						|
        case "^": result = left ^ right; break;
 | 
						|
        case "+": result = left + right; break;
 | 
						|
        case "*": result = left * right; break;
 | 
						|
        case "**": result = Math.pow(left, right); break;
 | 
						|
        case "/": result = left / right; break;
 | 
						|
        case "%": result = left % right; break;
 | 
						|
        case "-": result = left - right; break;
 | 
						|
        case "<<": result = left << right; break;
 | 
						|
        case ">>": result = left >> right; break;
 | 
						|
        case ">>>": result = left >>> right; break;
 | 
						|
        case "==": result = left == right; break;
 | 
						|
        case "===": result = left === right; break;
 | 
						|
        case "!=": result = left != right; break;
 | 
						|
        case "!==": result = left !== right; break;
 | 
						|
        case "<": result = left < right; break;
 | 
						|
        case "<=": result = left <= right; break;
 | 
						|
        case ">": result = left > right; break;
 | 
						|
        case ">=": result = left >= right; break;
 | 
						|
        default:
 | 
						|
            return this;
 | 
						|
    }
 | 
						|
    if (isNaN(result) && compressor.find_parent(AST_With)) {
 | 
						|
        // leave original expression as is
 | 
						|
        return this;
 | 
						|
    }
 | 
						|
    return result;
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_Conditional, function (compressor, depth) {
 | 
						|
    var condition = this.condition._eval(compressor, depth);
 | 
						|
    if (condition === this.condition)
 | 
						|
        return this;
 | 
						|
    var node = condition ? this.consequent : this.alternative;
 | 
						|
    var value = node._eval(compressor, depth);
 | 
						|
    return value === node ? this : value;
 | 
						|
});
 | 
						|
 | 
						|
// Set of AST_SymbolRef which are currently being evaluated.
 | 
						|
// Avoids infinite recursion of ._eval()
 | 
						|
const reentrant_ref_eval = new Set();
 | 
						|
def_eval(AST_SymbolRef, function (compressor, depth) {
 | 
						|
    if (reentrant_ref_eval.has(this))
 | 
						|
        return this;
 | 
						|
 | 
						|
    var fixed = this.fixed_value();
 | 
						|
    if (!fixed)
 | 
						|
        return this;
 | 
						|
 | 
						|
    reentrant_ref_eval.add(this);
 | 
						|
    const value = fixed._eval(compressor, depth);
 | 
						|
    reentrant_ref_eval.delete(this);
 | 
						|
 | 
						|
    if (value === fixed)
 | 
						|
        return this;
 | 
						|
 | 
						|
    if (value && typeof value == "object") {
 | 
						|
        var escaped = this.definition().escaped;
 | 
						|
        if (escaped && depth > escaped)
 | 
						|
            return this;
 | 
						|
    }
 | 
						|
    return value;
 | 
						|
});
 | 
						|
 | 
						|
const global_objs = { Array, Math, Number, Object, String };
 | 
						|
 | 
						|
const regexp_flags = new Set([
 | 
						|
    "dotAll",
 | 
						|
    "global",
 | 
						|
    "ignoreCase",
 | 
						|
    "multiline",
 | 
						|
    "sticky",
 | 
						|
    "unicode",
 | 
						|
]);
 | 
						|
 | 
						|
def_eval(AST_PropAccess, function (compressor, depth) {
 | 
						|
    let obj = this.expression._eval(compressor, depth + 1);
 | 
						|
    if (obj === nullish || (this.optional && obj == null)) return nullish;
 | 
						|
    if (compressor.option("unsafe")) {
 | 
						|
        var key = this.property;
 | 
						|
        if (key instanceof AST_Node) {
 | 
						|
            key = key._eval(compressor, depth);
 | 
						|
            if (key === this.property)
 | 
						|
                return this;
 | 
						|
        }
 | 
						|
        var exp = this.expression;
 | 
						|
        if (is_undeclared_ref(exp)) {
 | 
						|
 | 
						|
            var aa;
 | 
						|
            var first_arg = exp.name === "hasOwnProperty"
 | 
						|
                && key === "call"
 | 
						|
                && (aa = compressor.parent() && compressor.parent().args)
 | 
						|
                && (aa && aa[0]
 | 
						|
                    && aa[0].evaluate(compressor));
 | 
						|
 | 
						|
            first_arg = first_arg instanceof AST_Dot ? first_arg.expression : first_arg;
 | 
						|
 | 
						|
            if (first_arg == null || first_arg.thedef && first_arg.thedef.undeclared) {
 | 
						|
                return this.clone();
 | 
						|
            }
 | 
						|
            if (!is_pure_native_value(exp.name, key))
 | 
						|
                return this;
 | 
						|
            obj = global_objs[exp.name];
 | 
						|
        } else {
 | 
						|
            if (obj instanceof RegExp) {
 | 
						|
                if (key == "source") {
 | 
						|
                    return regexp_source_fix(obj.source);
 | 
						|
                } else if (key == "flags" || regexp_flags.has(key)) {
 | 
						|
                    return obj[key];
 | 
						|
                }
 | 
						|
            }
 | 
						|
            if (!obj || obj === exp || !HOP(obj, key))
 | 
						|
                return this;
 | 
						|
 | 
						|
            if (typeof obj == "function")
 | 
						|
                switch (key) {
 | 
						|
                    case "name":
 | 
						|
                        return obj.node.name ? obj.node.name.name : "";
 | 
						|
                    case "length":
 | 
						|
                        return obj.node.length_property();
 | 
						|
                    default:
 | 
						|
                        return this;
 | 
						|
                }
 | 
						|
        }
 | 
						|
        return obj[key];
 | 
						|
    }
 | 
						|
    return this;
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_Chain, function (compressor, depth) {
 | 
						|
    const evaluated = this.expression._eval(compressor, depth);
 | 
						|
    return evaluated === nullish
 | 
						|
        ? undefined
 | 
						|
        : evaluated === this.expression
 | 
						|
          ? this
 | 
						|
          : evaluated;
 | 
						|
});
 | 
						|
 | 
						|
def_eval(AST_Call, function (compressor, depth) {
 | 
						|
    var exp = this.expression;
 | 
						|
 | 
						|
    const callee = exp._eval(compressor, depth);
 | 
						|
    if (callee === nullish || (this.optional && callee == null)) return nullish;
 | 
						|
 | 
						|
    if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
 | 
						|
        var key = exp.property;
 | 
						|
        if (key instanceof AST_Node) {
 | 
						|
            key = key._eval(compressor, depth);
 | 
						|
            if (key === exp.property)
 | 
						|
                return this;
 | 
						|
        }
 | 
						|
        var val;
 | 
						|
        var e = exp.expression;
 | 
						|
        if (is_undeclared_ref(e)) {
 | 
						|
            var first_arg = e.name === "hasOwnProperty" &&
 | 
						|
                key === "call" &&
 | 
						|
                (this.args[0] && this.args[0].evaluate(compressor));
 | 
						|
 | 
						|
            first_arg = first_arg instanceof AST_Dot ? first_arg.expression : first_arg;
 | 
						|
 | 
						|
            if ((first_arg == null || first_arg.thedef && first_arg.thedef.undeclared)) {
 | 
						|
                return this.clone();
 | 
						|
            }
 | 
						|
            if (!is_pure_native_fn(e.name, key)) return this;
 | 
						|
            val = global_objs[e.name];
 | 
						|
        } else {
 | 
						|
            val = e._eval(compressor, depth + 1);
 | 
						|
            if (val === e || !val)
 | 
						|
                return this;
 | 
						|
            if (!is_pure_native_method(val.constructor.name, key))
 | 
						|
                return this;
 | 
						|
        }
 | 
						|
        var args = [];
 | 
						|
        for (var i = 0, len = this.args.length; i < len; i++) {
 | 
						|
            var arg = this.args[i];
 | 
						|
            var value = arg._eval(compressor, depth);
 | 
						|
            if (arg === value)
 | 
						|
                return this;
 | 
						|
            if (arg instanceof AST_Lambda)
 | 
						|
                return this;
 | 
						|
            args.push(value);
 | 
						|
        }
 | 
						|
        try {
 | 
						|
            return val[key].apply(val, args);
 | 
						|
        } catch (ex) {
 | 
						|
            // We don't really care
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return this;
 | 
						|
});
 | 
						|
 | 
						|
// Also a subclass of AST_Call
 | 
						|
def_eval(AST_New, return_this);
 |