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
			| 
											3 years ago
										 | /*********************************************************************** | ||
|  | 
 | ||
|  |   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); | ||
|  | } |