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.
		
		
		
		
		
			
		
			
				
					
					
						
							346 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
	
	
							346 lines
						
					
					
						
							11 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 {
 | 
						|
    AST_Array,
 | 
						|
    AST_Arrow,
 | 
						|
    AST_BlockStatement,
 | 
						|
    AST_Call,
 | 
						|
    AST_Class,
 | 
						|
    AST_Const,
 | 
						|
    AST_Constant,
 | 
						|
    AST_DefClass,
 | 
						|
    AST_Defun,
 | 
						|
    AST_EmptyStatement,
 | 
						|
    AST_Export,
 | 
						|
    AST_False,
 | 
						|
    AST_Function,
 | 
						|
    AST_Import,
 | 
						|
    AST_Infinity,
 | 
						|
    AST_LabeledStatement,
 | 
						|
    AST_Lambda,
 | 
						|
    AST_Let,
 | 
						|
    AST_LoopControl,
 | 
						|
    AST_NaN,
 | 
						|
    AST_Node,
 | 
						|
    AST_Null,
 | 
						|
    AST_Number,
 | 
						|
    AST_Object,
 | 
						|
    AST_ObjectKeyVal,
 | 
						|
    AST_PropAccess,
 | 
						|
    AST_RegExp,
 | 
						|
    AST_Scope,
 | 
						|
    AST_Sequence,
 | 
						|
    AST_SimpleStatement,
 | 
						|
    AST_Statement,
 | 
						|
    AST_String,
 | 
						|
    AST_SymbolRef,
 | 
						|
    AST_True,
 | 
						|
    AST_UnaryPrefix,
 | 
						|
    AST_Undefined,
 | 
						|
 | 
						|
    TreeWalker,
 | 
						|
    walk,
 | 
						|
    walk_abort,
 | 
						|
    walk_parent,
 | 
						|
} from "../ast.js";
 | 
						|
import { make_node, regexp_source_fix, string_template, makePredicate } from "../utils/index.js";
 | 
						|
import { first_in_statement } from "../utils/first_in_statement.js";
 | 
						|
import { has_flag, TOP } from "./compressor-flags.js";
 | 
						|
 | 
						|
export function merge_sequence(array, node) {
 | 
						|
    if (node instanceof AST_Sequence) {
 | 
						|
        array.push(...node.expressions);
 | 
						|
    } else {
 | 
						|
        array.push(node);
 | 
						|
    }
 | 
						|
    return array;
 | 
						|
}
 | 
						|
 | 
						|
export function make_sequence(orig, expressions) {
 | 
						|
    if (expressions.length == 1) return expressions[0];
 | 
						|
    if (expressions.length == 0) throw new Error("trying to create a sequence with length zero!");
 | 
						|
    return make_node(AST_Sequence, orig, {
 | 
						|
        expressions: expressions.reduce(merge_sequence, [])
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
export function make_node_from_constant(val, orig) {
 | 
						|
    switch (typeof val) {
 | 
						|
      case "string":
 | 
						|
        return make_node(AST_String, orig, {
 | 
						|
            value: val
 | 
						|
        });
 | 
						|
      case "number":
 | 
						|
        if (isNaN(val)) return make_node(AST_NaN, orig);
 | 
						|
        if (isFinite(val)) {
 | 
						|
            return 1 / val < 0 ? make_node(AST_UnaryPrefix, orig, {
 | 
						|
                operator: "-",
 | 
						|
                expression: make_node(AST_Number, orig, { value: -val })
 | 
						|
            }) : make_node(AST_Number, orig, { value: val });
 | 
						|
        }
 | 
						|
        return val < 0 ? make_node(AST_UnaryPrefix, orig, {
 | 
						|
            operator: "-",
 | 
						|
            expression: make_node(AST_Infinity, orig)
 | 
						|
        }) : make_node(AST_Infinity, orig);
 | 
						|
      case "boolean":
 | 
						|
        return make_node(val ? AST_True : AST_False, orig);
 | 
						|
      case "undefined":
 | 
						|
        return make_node(AST_Undefined, orig);
 | 
						|
      default:
 | 
						|
        if (val === null) {
 | 
						|
            return make_node(AST_Null, orig, { value: null });
 | 
						|
        }
 | 
						|
        if (val instanceof RegExp) {
 | 
						|
            return make_node(AST_RegExp, orig, {
 | 
						|
                value: {
 | 
						|
                    source: regexp_source_fix(val.source),
 | 
						|
                    flags: val.flags
 | 
						|
                }
 | 
						|
            });
 | 
						|
        }
 | 
						|
        throw new Error(string_template("Can't handle constant of type: {type}", {
 | 
						|
            type: typeof val
 | 
						|
        }));
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
export function best_of_expression(ast1, ast2) {
 | 
						|
    return ast1.size() > ast2.size() ? ast2 : ast1;
 | 
						|
}
 | 
						|
 | 
						|
export function best_of_statement(ast1, ast2) {
 | 
						|
    return best_of_expression(
 | 
						|
        make_node(AST_SimpleStatement, ast1, {
 | 
						|
            body: ast1
 | 
						|
        }),
 | 
						|
        make_node(AST_SimpleStatement, ast2, {
 | 
						|
            body: ast2
 | 
						|
        })
 | 
						|
    ).body;
 | 
						|
}
 | 
						|
 | 
						|
/** Find which node is smaller, and return that */
 | 
						|
export function best_of(compressor, ast1, ast2) {
 | 
						|
    if (first_in_statement(compressor)) {
 | 
						|
        return best_of_statement(ast1, ast2);
 | 
						|
    } else {
 | 
						|
        return best_of_expression(ast1, ast2);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/** Simplify an object property's key, if possible */
 | 
						|
export function get_simple_key(key) {
 | 
						|
    if (key instanceof AST_Constant) {
 | 
						|
        return key.getValue();
 | 
						|
    }
 | 
						|
    if (key instanceof AST_UnaryPrefix
 | 
						|
        && key.operator == "void"
 | 
						|
        && key.expression instanceof AST_Constant) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    return key;
 | 
						|
}
 | 
						|
 | 
						|
export function read_property(obj, key) {
 | 
						|
    key = get_simple_key(key);
 | 
						|
    if (key instanceof AST_Node) return;
 | 
						|
 | 
						|
    var value;
 | 
						|
    if (obj instanceof AST_Array) {
 | 
						|
        var elements = obj.elements;
 | 
						|
        if (key == "length") return make_node_from_constant(elements.length, obj);
 | 
						|
        if (typeof key == "number" && key in elements) value = elements[key];
 | 
						|
    } else if (obj instanceof AST_Object) {
 | 
						|
        key = "" + key;
 | 
						|
        var props = obj.properties;
 | 
						|
        for (var i = props.length; --i >= 0;) {
 | 
						|
            var prop = props[i];
 | 
						|
            if (!(prop instanceof AST_ObjectKeyVal)) return;
 | 
						|
            if (!value && props[i].key === key) value = props[i].value;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return value instanceof AST_SymbolRef && value.fixed_value() || value;
 | 
						|
}
 | 
						|
 | 
						|
export function has_break_or_continue(loop, parent) {
 | 
						|
    var found = false;
 | 
						|
    var tw = new TreeWalker(function(node) {
 | 
						|
        if (found || node instanceof AST_Scope) return true;
 | 
						|
        if (node instanceof AST_LoopControl && tw.loopcontrol_target(node) === loop) {
 | 
						|
            return found = true;
 | 
						|
        }
 | 
						|
    });
 | 
						|
    if (parent instanceof AST_LabeledStatement) tw.push(parent);
 | 
						|
    tw.push(loop);
 | 
						|
    loop.body.walk(tw);
 | 
						|
    return found;
 | 
						|
}
 | 
						|
 | 
						|
// we shouldn't compress (1,func)(something) to
 | 
						|
// func(something) because that changes the meaning of
 | 
						|
// the func (becomes lexical instead of global).
 | 
						|
export function maintain_this_binding(parent, orig, val) {
 | 
						|
    if (
 | 
						|
        parent instanceof AST_UnaryPrefix && parent.operator == "delete"
 | 
						|
        || parent instanceof AST_Call && parent.expression === orig
 | 
						|
            && (
 | 
						|
                val instanceof AST_PropAccess
 | 
						|
                || val instanceof AST_SymbolRef && val.name == "eval"
 | 
						|
            )
 | 
						|
    ) {
 | 
						|
        const zero = make_node(AST_Number, orig, { value: 0 });
 | 
						|
        return make_sequence(orig, [ zero, val ]);
 | 
						|
    } else {
 | 
						|
        return val;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
export function is_func_expr(node) {
 | 
						|
    return node instanceof AST_Arrow || node instanceof AST_Function;
 | 
						|
}
 | 
						|
 | 
						|
export function is_iife_call(node) {
 | 
						|
    // Used to determine whether the node can benefit from negation.
 | 
						|
    // Not the case with arrow functions (you need an extra set of parens).
 | 
						|
    if (node.TYPE != "Call") return false;
 | 
						|
    return node.expression instanceof AST_Function || is_iife_call(node.expression);
 | 
						|
}
 | 
						|
 | 
						|
export function is_empty(thing) {
 | 
						|
    if (thing === null) return true;
 | 
						|
    if (thing instanceof AST_EmptyStatement) return true;
 | 
						|
    if (thing instanceof AST_BlockStatement) return thing.body.length == 0;
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
export const identifier_atom = makePredicate("Infinity NaN undefined");
 | 
						|
export function is_identifier_atom(node) {
 | 
						|
    return node instanceof AST_Infinity
 | 
						|
        || node instanceof AST_NaN
 | 
						|
        || node instanceof AST_Undefined;
 | 
						|
}
 | 
						|
 | 
						|
/** Check if this is a SymbolRef node which has one def of a certain AST type */
 | 
						|
export function is_ref_of(ref, type) {
 | 
						|
    if (!(ref instanceof AST_SymbolRef)) return false;
 | 
						|
    var orig = ref.definition().orig;
 | 
						|
    for (var i = orig.length; --i >= 0;) {
 | 
						|
        if (orig[i] instanceof type) return true;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/**Can we turn { block contents... } into just the block contents ?
 | 
						|
 * Not if one of these is inside.
 | 
						|
 **/
 | 
						|
export function can_be_evicted_from_block(node) {
 | 
						|
    return !(
 | 
						|
        node instanceof AST_DefClass ||
 | 
						|
        node instanceof AST_Defun ||
 | 
						|
        node instanceof AST_Let ||
 | 
						|
        node instanceof AST_Const ||
 | 
						|
        node instanceof AST_Export ||
 | 
						|
        node instanceof AST_Import
 | 
						|
    );
 | 
						|
}
 | 
						|
 | 
						|
export function as_statement_array(thing) {
 | 
						|
    if (thing === null) return [];
 | 
						|
    if (thing instanceof AST_BlockStatement) return thing.body;
 | 
						|
    if (thing instanceof AST_EmptyStatement) return [];
 | 
						|
    if (thing instanceof AST_Statement) return [ thing ];
 | 
						|
    throw new Error("Can't convert thing to statement array");
 | 
						|
}
 | 
						|
 | 
						|
export function is_reachable(scope_node, defs) {
 | 
						|
    const find_ref = node => {
 | 
						|
        if (node instanceof AST_SymbolRef && defs.includes(node.definition())) {
 | 
						|
            return walk_abort;
 | 
						|
        }
 | 
						|
    };
 | 
						|
 | 
						|
    return walk_parent(scope_node, (node, info) => {
 | 
						|
        if (node instanceof AST_Scope && node !== scope_node) {
 | 
						|
            var parent = info.parent();
 | 
						|
 | 
						|
            if (
 | 
						|
                parent instanceof AST_Call
 | 
						|
                && parent.expression === node
 | 
						|
                // Async/Generators aren't guaranteed to sync evaluate all of
 | 
						|
                // their body steps, so it's possible they close over the variable.
 | 
						|
                && !(node.async || node.is_generator)
 | 
						|
            ) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            if (walk(node, find_ref)) return walk_abort;
 | 
						|
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
    });
 | 
						|
}
 | 
						|
 | 
						|
/** Check if a ref refers to the name of a function/class it's defined within */
 | 
						|
export function is_recursive_ref(compressor, def) {
 | 
						|
    var node;
 | 
						|
    for (var i = 0; node = compressor.parent(i); i++) {
 | 
						|
        if (node instanceof AST_Lambda || node instanceof AST_Class) {
 | 
						|
            var name = node.name;
 | 
						|
            if (name && name.definition() === def) {
 | 
						|
                return true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
// TODO this only works with AST_Defun, shouldn't it work for other ways of defining functions?
 | 
						|
export function retain_top_func(fn, compressor) {
 | 
						|
    return compressor.top_retain
 | 
						|
        && fn instanceof AST_Defun
 | 
						|
        && has_flag(fn, TOP)
 | 
						|
        && fn.name
 | 
						|
        && compressor.top_retain(fn.name);
 | 
						|
}
 |