123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- /***********************************************************************
- 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_Accessor,
- AST_Assign,
- AST_BlockStatement,
- AST_Call,
- AST_Class,
- AST_ClassExpression,
- AST_ClassStaticBlock,
- AST_DefaultAssign,
- AST_DefClass,
- AST_Definitions,
- AST_Defun,
- AST_Destructuring,
- AST_EmptyStatement,
- AST_Expansion,
- AST_Export,
- AST_For,
- AST_ForIn,
- AST_Function,
- AST_LabeledStatement,
- AST_Lambda,
- AST_Number,
- AST_Scope,
- AST_Sequence,
- AST_SimpleStatement,
- AST_SymbolBlockDeclaration,
- AST_SymbolCatch,
- AST_SymbolDeclaration,
- AST_SymbolFunarg,
- AST_SymbolRef,
- AST_SymbolVar,
- AST_Toplevel,
- AST_Unary,
- AST_Var,
- TreeTransformer,
- TreeWalker,
- walk,
- } from "../ast.js";
- import {
- keep_name,
- make_node,
- map_add,
- MAP,
- remove,
- return_false,
- } from "../utils/index.js";
- import { SymbolDef } from "../scope.js";
- import {
- WRITE_ONLY,
- UNUSED,
- has_flag,
- set_flag,
- } from "./compressor-flags.js";
- import {
- make_sequence,
- maintain_this_binding,
- is_empty,
- is_ref_of,
- can_be_evicted_from_block,
- requires_sequence_to_maintain_binding,
- } from "./common.js";
- const r_keep_assign = /keep_assign/;
- /** Drop unused variables from this scope */
- AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
- if (!compressor.option("unused")) return;
- if (compressor.has_directive("use asm")) return;
- if (!this.variables) return; // not really a scope (eg: AST_Class)
- var self = this;
- if (self.pinned()) return;
- var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
- var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
- const assign_as_unused = r_keep_assign.test(compressor.option("unused")) ? return_false : function(node) {
- if (node instanceof AST_Assign
- && !node.logical
- && (has_flag(node, WRITE_ONLY) || node.operator == "=")
- ) {
- return node.left;
- }
- if (node instanceof AST_Unary && has_flag(node, WRITE_ONLY)) {
- return node.expression;
- }
- };
- var in_use_ids = new Map();
- var fixed_ids = new Map();
- if (self instanceof AST_Toplevel && compressor.top_retain) {
- self.variables.forEach(function(def) {
- if (compressor.top_retain(def)) {
- in_use_ids.set(def.id, def);
- }
- });
- }
- var var_defs_by_id = new Map();
- var initializations = new Map();
- // pass 1: find out which symbols are directly used in
- // this scope (not in nested scopes).
- var scope = this;
- var tw = new TreeWalker(function(node, descend) {
- if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) {
- node.argnames.forEach(function(argname) {
- if (!(argname instanceof AST_SymbolDeclaration)) return;
- var def = argname.definition();
- in_use_ids.set(def.id, def);
- });
- }
- if (node === self) return;
- if (node instanceof AST_Class && node.has_side_effects(compressor)) {
- if (node.is_self_referential()) {
- descend();
- } else {
- node.visit_nondeferred_class_parts(tw);
- }
- }
- if (node instanceof AST_Defun || node instanceof AST_DefClass) {
- var node_def = node.name.definition();
- const in_export = tw.parent() instanceof AST_Export;
- if (in_export || !drop_funcs && scope === self) {
- if (node_def.global) {
- in_use_ids.set(node_def.id, node_def);
- }
- }
- map_add(initializations, node_def.id, node);
- return true; // don't go in nested scopes
- }
- // In the root scope, we drop things. In inner scopes, we just check for uses.
- const in_root_scope = scope === self;
- if (node instanceof AST_SymbolFunarg && in_root_scope) {
- map_add(var_defs_by_id, node.definition().id, node);
- }
- if (node instanceof AST_Definitions && in_root_scope) {
- const in_export = tw.parent() instanceof AST_Export;
- node.definitions.forEach(function(def) {
- if (def.name instanceof AST_SymbolVar) {
- map_add(var_defs_by_id, def.name.definition().id, def);
- }
- if (in_export || !drop_vars) {
- walk(def.name, node => {
- if (node instanceof AST_SymbolDeclaration) {
- const def = node.definition();
- if (def.global) {
- in_use_ids.set(def.id, def);
- }
- }
- });
- }
- if (def.name instanceof AST_Destructuring) {
- def.walk(tw);
- }
- if (def.name instanceof AST_SymbolDeclaration && def.value) {
- var node_def = def.name.definition();
- map_add(initializations, node_def.id, def.value);
- if (!node_def.chained && def.name.fixed_value() === def.value) {
- fixed_ids.set(node_def.id, def);
- }
- if (def.value.has_side_effects(compressor)) {
- def.value.walk(tw);
- }
- }
- });
- return true;
- }
- return scan_ref_scoped(node, descend);
- });
- self.walk(tw);
- // pass 2: for every used symbol we need to walk its
- // initialization code to figure out if it uses other
- // symbols (that may not be in_use).
- tw = new TreeWalker(scan_ref_scoped);
- in_use_ids.forEach(function (def) {
- var init = initializations.get(def.id);
- if (init) init.forEach(function(init) {
- init.walk(tw);
- });
- });
- // pass 3: we should drop declarations not in_use
- var tt = new TreeTransformer(
- function before(node, descend, in_list) {
- var parent = tt.parent();
- if (drop_vars) {
- const sym = assign_as_unused(node);
- if (sym instanceof AST_SymbolRef) {
- var def = sym.definition();
- var in_use = in_use_ids.has(def.id);
- if (node instanceof AST_Assign) {
- if (!in_use || fixed_ids.has(def.id) && fixed_ids.get(def.id) !== node) {
- const assignee = node.right.transform(tt);
- if (!in_use && !assignee.has_side_effects(compressor) && !is_used_in_expression(tt)) {
- return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
- }
- return maintain_this_binding(parent, node, assignee);
- }
- } else if (!in_use) {
- return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
- }
- }
- }
- if (scope !== self) return;
- var def;
- if (node.name
- && (node instanceof AST_ClassExpression
- && !keep_name(compressor.option("keep_classnames"), (def = node.name.definition()).name)
- || node instanceof AST_Function
- && !keep_name(compressor.option("keep_fnames"), (def = node.name.definition()).name))) {
- // any declarations with same name will overshadow
- // name of this anonymous function and can therefore
- // never be used anywhere
- if (!in_use_ids.has(def.id) || def.orig.length > 1) node.name = null;
- }
- if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
- var trim =
- !compressor.option("keep_fargs")
- // Is this an IIFE that won't refer to its name?
- || parent instanceof AST_Call
- && parent.expression === node
- && !node.pinned()
- && (!node.name || node.name.unreferenced());
- for (var a = node.argnames, i = a.length; --i >= 0;) {
- var sym = a[i];
- if (sym instanceof AST_Expansion) {
- sym = sym.expression;
- }
- if (sym instanceof AST_DefaultAssign) {
- sym = sym.left;
- }
- // Do not drop destructuring arguments.
- // They constitute a type assertion of sorts
- if (
- !(sym instanceof AST_Destructuring)
- && !in_use_ids.has(sym.definition().id)
- ) {
- set_flag(sym, UNUSED);
- if (trim) {
- a.pop();
- }
- } else {
- trim = false;
- }
- }
- }
- if (node instanceof AST_DefClass && node !== self) {
- const def = node.name.definition();
- descend(node, this);
- const keep_class = def.global && !drop_funcs || in_use_ids.has(def.id);
- if (!keep_class) {
- const kept = node.drop_side_effect_free(compressor);
- if (kept == null) {
- def.eliminated++;
- return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
- }
- return kept;
- }
- return node;
- }
- if (node instanceof AST_Defun && node !== self) {
- const def = node.name.definition();
- const keep = def.global && !drop_funcs || in_use_ids.has(def.id);
- if (!keep) {
- def.eliminated++;
- return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
- }
- }
- if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
- var drop_block = !(parent instanceof AST_Toplevel) && !(node instanceof AST_Var);
- // place uninitialized names at the start
- var body = [], head = [], tail = [];
- // for unused names whose initialization has
- // side effects, we can cascade the init. code
- // into the next one, or next statement.
- var side_effects = [];
- node.definitions.forEach(function(def) {
- if (def.value) def.value = def.value.transform(tt);
- var is_destructure = def.name instanceof AST_Destructuring;
- var sym = is_destructure
- ? new SymbolDef(null, { name: "<destructure>" }) /* fake SymbolDef */
- : def.name.definition();
- if (drop_block && sym.global) return tail.push(def);
- if (!(drop_vars || drop_block)
- || is_destructure
- && (def.name.names.length
- || def.name.is_array
- || compressor.option("pure_getters") != true)
- || in_use_ids.has(sym.id)
- ) {
- if (def.value && fixed_ids.has(sym.id) && fixed_ids.get(sym.id) !== def) {
- def.value = def.value.drop_side_effect_free(compressor);
- }
- if (def.name instanceof AST_SymbolVar) {
- var var_defs = var_defs_by_id.get(sym.id);
- if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
- if (def.value) {
- var ref = make_node(AST_SymbolRef, def.name, def.name);
- sym.references.push(ref);
- var assign = make_node(AST_Assign, def, {
- operator: "=",
- logical: false,
- left: ref,
- right: def.value
- });
- if (fixed_ids.get(sym.id) === def) {
- fixed_ids.set(sym.id, assign);
- }
- side_effects.push(assign.transform(tt));
- }
- remove(var_defs, def);
- sym.eliminated++;
- return;
- }
- }
- if (def.value) {
- if (side_effects.length > 0) {
- if (tail.length > 0) {
- side_effects.push(def.value);
- def.value = make_sequence(def.value, side_effects);
- } else {
- body.push(make_node(AST_SimpleStatement, node, {
- body: make_sequence(node, side_effects)
- }));
- }
- side_effects = [];
- }
- tail.push(def);
- } else {
- head.push(def);
- }
- } else if (sym.orig[0] instanceof AST_SymbolCatch) {
- var value = def.value && def.value.drop_side_effect_free(compressor);
- if (value) side_effects.push(value);
- def.value = null;
- head.push(def);
- } else {
- var value = def.value && def.value.drop_side_effect_free(compressor);
- if (value) {
- side_effects.push(value);
- }
- sym.eliminated++;
- }
- });
- if (head.length > 0 || tail.length > 0) {
- node.definitions = head.concat(tail);
- body.push(node);
- }
- if (side_effects.length > 0) {
- body.push(make_node(AST_SimpleStatement, node, {
- body: make_sequence(node, side_effects)
- }));
- }
- switch (body.length) {
- case 0:
- return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
- case 1:
- return body[0];
- default:
- return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { body });
- }
- }
- // certain combination of unused name + side effect leads to:
- // https://github.com/mishoo/UglifyJS2/issues/44
- // https://github.com/mishoo/UglifyJS2/issues/1830
- // https://github.com/mishoo/UglifyJS2/issues/1838
- // that's an invalid AST.
- // We fix it at this stage by moving the `var` outside the `for`.
- if (node instanceof AST_For) {
- descend(node, this);
- var block;
- if (node.init instanceof AST_BlockStatement) {
- block = node.init;
- node.init = block.body.pop();
- block.body.push(node);
- }
- if (node.init instanceof AST_SimpleStatement) {
- node.init = node.init.body;
- } else if (is_empty(node.init)) {
- node.init = null;
- }
- return !block ? node : in_list ? MAP.splice(block.body) : block;
- }
- if (node instanceof AST_LabeledStatement
- && node.body instanceof AST_For
- ) {
- descend(node, this);
- if (node.body instanceof AST_BlockStatement) {
- var block = node.body;
- node.body = block.body.pop();
- block.body.push(node);
- return in_list ? MAP.splice(block.body) : block;
- }
- return node;
- }
- if (node instanceof AST_BlockStatement) {
- descend(node, this);
- if (in_list && node.body.every(can_be_evicted_from_block)) {
- return MAP.splice(node.body);
- }
- return node;
- }
- if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
- const save_scope = scope;
- scope = node;
- descend(node, this);
- scope = save_scope;
- return node;
- }
- },
- function after(node, in_list) {
- if (node instanceof AST_Sequence) {
- switch (node.expressions.length) {
- case 0: return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
- case 1: return node.expressions[0];
- }
- }
- }
- );
- self.transform(tt);
- function scan_ref_scoped(node, descend) {
- var node_def;
- const sym = assign_as_unused(node);
- if (sym instanceof AST_SymbolRef
- && !is_ref_of(node.left, AST_SymbolBlockDeclaration)
- && self.variables.get(sym.name) === (node_def = sym.definition())
- ) {
- if (node instanceof AST_Assign) {
- node.right.walk(tw);
- if (!node_def.chained && node.left.fixed_value() === node.right) {
- fixed_ids.set(node_def.id, node);
- }
- }
- return true;
- }
- if (node instanceof AST_SymbolRef) {
- node_def = node.definition();
- if (!in_use_ids.has(node_def.id)) {
- in_use_ids.set(node_def.id, node_def);
- if (node_def.orig[0] instanceof AST_SymbolCatch) {
- const redef = node_def.scope.is_block_scope()
- && node_def.scope.get_defun_scope().variables.get(node_def.name);
- if (redef) in_use_ids.set(redef.id, redef);
- }
- }
- return true;
- }
- if (node instanceof AST_Class) {
- descend();
- return true;
- }
- if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
- var save_scope = scope;
- scope = node;
- descend();
- scope = save_scope;
- return true;
- }
- }
- });
- /**
- * Check if a node may be used by the expression it's in
- * void (0, 1, {node}, 2) -> false
- * console.log(0, {node}) -> true
- */
- function is_used_in_expression(tw) {
- for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
- if (parent instanceof AST_Sequence) {
- const nth_expression = parent.expressions.indexOf(node);
- if (nth_expression !== parent.expressions.length - 1) {
- // Detect (0, x.noThis)() constructs
- const grandparent = tw.parent(p + 2);
- if (
- parent.expressions.length > 2
- || parent.expressions.length === 1
- || !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
- ) {
- return false;
- }
- return true;
- } else {
- continue;
- }
- }
- if (parent instanceof AST_Unary) {
- const op = parent.operator;
- if (op === "void") {
- return false;
- }
- if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
- continue;
- }
- }
- return true;
- }
- return true;
- }
|