| 1 | /***********************************************************************
|
|---|
| 2 |
|
|---|
| 3 | A JavaScript tokenizer / parser / beautifier / compressor.
|
|---|
| 4 | https://github.com/mishoo/UglifyJS2
|
|---|
| 5 |
|
|---|
| 6 | -------------------------------- (C) ---------------------------------
|
|---|
| 7 |
|
|---|
| 8 | Author: Mihai Bazon
|
|---|
| 9 | <mihai.bazon@gmail.com>
|
|---|
| 10 | http://mihai.bazon.net/blog
|
|---|
| 11 |
|
|---|
| 12 | Distributed under the BSD license:
|
|---|
| 13 |
|
|---|
| 14 | Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
|
|---|
| 15 |
|
|---|
| 16 | Redistribution and use in source and binary forms, with or without
|
|---|
| 17 | modification, are permitted provided that the following conditions
|
|---|
| 18 | are met:
|
|---|
| 19 |
|
|---|
| 20 | * Redistributions of source code must retain the above
|
|---|
| 21 | copyright notice, this list of conditions and the following
|
|---|
| 22 | disclaimer.
|
|---|
| 23 |
|
|---|
| 24 | * Redistributions in binary form must reproduce the above
|
|---|
| 25 | copyright notice, this list of conditions and the following
|
|---|
| 26 | disclaimer in the documentation and/or other materials
|
|---|
| 27 | provided with the distribution.
|
|---|
| 28 |
|
|---|
| 29 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
|
|---|
| 30 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|---|
| 31 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|---|
| 32 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
|
|---|
| 33 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
|---|
| 34 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|---|
| 35 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|---|
| 36 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|---|
| 37 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
|---|
| 38 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
|---|
| 39 | THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|---|
| 40 | SUCH DAMAGE.
|
|---|
| 41 |
|
|---|
| 42 | ***********************************************************************/
|
|---|
| 43 |
|
|---|
| 44 | import {
|
|---|
| 45 | AST_Accessor,
|
|---|
| 46 | AST_Array,
|
|---|
| 47 | AST_Arrow,
|
|---|
| 48 | AST_Assign,
|
|---|
| 49 | AST_BigInt,
|
|---|
| 50 | AST_Binary,
|
|---|
| 51 | AST_Block,
|
|---|
| 52 | AST_BlockStatement,
|
|---|
| 53 | AST_Boolean,
|
|---|
| 54 | AST_Break,
|
|---|
| 55 | AST_Call,
|
|---|
| 56 | AST_Catch,
|
|---|
| 57 | AST_Chain,
|
|---|
| 58 | AST_Class,
|
|---|
| 59 | AST_ClassProperty,
|
|---|
| 60 | AST_ClassStaticBlock,
|
|---|
| 61 | AST_ConciseMethod,
|
|---|
| 62 | AST_Conditional,
|
|---|
| 63 | AST_Const,
|
|---|
| 64 | AST_Constant,
|
|---|
| 65 | AST_Debugger,
|
|---|
| 66 | AST_Default,
|
|---|
| 67 | AST_DefaultAssign,
|
|---|
| 68 | AST_Definitions,
|
|---|
| 69 | AST_Defun,
|
|---|
| 70 | AST_Destructuring,
|
|---|
| 71 | AST_Directive,
|
|---|
| 72 | AST_Do,
|
|---|
| 73 | AST_Dot,
|
|---|
| 74 | AST_DotHash,
|
|---|
| 75 | AST_DWLoop,
|
|---|
| 76 | AST_EmptyStatement,
|
|---|
| 77 | AST_Exit,
|
|---|
| 78 | AST_Expansion,
|
|---|
| 79 | AST_Export,
|
|---|
| 80 | AST_False,
|
|---|
| 81 | AST_For,
|
|---|
| 82 | AST_ForIn,
|
|---|
| 83 | AST_Function,
|
|---|
| 84 | AST_Hole,
|
|---|
| 85 | AST_If,
|
|---|
| 86 | AST_Import,
|
|---|
| 87 | AST_Infinity,
|
|---|
| 88 | AST_LabeledStatement,
|
|---|
| 89 | AST_Lambda,
|
|---|
| 90 | AST_Let,
|
|---|
| 91 | AST_NaN,
|
|---|
| 92 | AST_New,
|
|---|
| 93 | AST_Node,
|
|---|
| 94 | AST_Null,
|
|---|
| 95 | AST_Number,
|
|---|
| 96 | AST_Object,
|
|---|
| 97 | AST_ObjectKeyVal,
|
|---|
| 98 | AST_ObjectProperty,
|
|---|
| 99 | AST_PrefixedTemplateString,
|
|---|
| 100 | AST_PropAccess,
|
|---|
| 101 | AST_RegExp,
|
|---|
| 102 | AST_Return,
|
|---|
| 103 | AST_Scope,
|
|---|
| 104 | AST_Sequence,
|
|---|
| 105 | AST_SimpleStatement,
|
|---|
| 106 | AST_Statement,
|
|---|
| 107 | AST_String,
|
|---|
| 108 | AST_Sub,
|
|---|
| 109 | AST_Switch,
|
|---|
| 110 | AST_SwitchBranch,
|
|---|
| 111 | AST_Symbol,
|
|---|
| 112 | AST_SymbolClassProperty,
|
|---|
| 113 | AST_SymbolDeclaration,
|
|---|
| 114 | AST_SymbolDefun,
|
|---|
| 115 | AST_SymbolExport,
|
|---|
| 116 | AST_SymbolFunarg,
|
|---|
| 117 | AST_SymbolLambda,
|
|---|
| 118 | AST_SymbolLet,
|
|---|
| 119 | AST_SymbolMethod,
|
|---|
| 120 | AST_SymbolRef,
|
|---|
| 121 | AST_SymbolUsing,
|
|---|
| 122 | AST_TemplateString,
|
|---|
| 123 | AST_This,
|
|---|
| 124 | AST_Toplevel,
|
|---|
| 125 | AST_True,
|
|---|
| 126 | AST_Try,
|
|---|
| 127 | AST_Unary,
|
|---|
| 128 | AST_UnaryPostfix,
|
|---|
| 129 | AST_UnaryPrefix,
|
|---|
| 130 | AST_Undefined,
|
|---|
| 131 | AST_Using,
|
|---|
| 132 | AST_Var,
|
|---|
| 133 | AST_VarDef,
|
|---|
| 134 | AST_While,
|
|---|
| 135 | AST_With,
|
|---|
| 136 | AST_Yield,
|
|---|
| 137 |
|
|---|
| 138 | TreeTransformer,
|
|---|
| 139 | TreeWalker,
|
|---|
| 140 | walk,
|
|---|
| 141 | walk_abort,
|
|---|
| 142 |
|
|---|
| 143 | _NOINLINE,
|
|---|
| 144 | } from "../ast.js";
|
|---|
| 145 | import {
|
|---|
| 146 | defaults,
|
|---|
| 147 | HOP,
|
|---|
| 148 | make_node,
|
|---|
| 149 | make_void_0,
|
|---|
| 150 | makePredicate,
|
|---|
| 151 | MAP,
|
|---|
| 152 | remove,
|
|---|
| 153 | return_false,
|
|---|
| 154 | return_true,
|
|---|
| 155 | regexp_source_fix,
|
|---|
| 156 | has_annotation,
|
|---|
| 157 | regexp_is_safe,
|
|---|
| 158 | } from "../utils/index.js";
|
|---|
| 159 | import { first_in_statement } from "../utils/first_in_statement.js";
|
|---|
| 160 | import { equivalent_to } from "../equivalent-to.js";
|
|---|
| 161 | import {
|
|---|
| 162 | is_basic_identifier_string,
|
|---|
| 163 | JS_Parse_Error,
|
|---|
| 164 | parse,
|
|---|
| 165 | PRECEDENCE,
|
|---|
| 166 | } from "../parse.js";
|
|---|
| 167 | import { OutputStream } from "../output.js";
|
|---|
| 168 | import { base54, format_mangler_options } from "../scope.js";
|
|---|
| 169 | import "../size.js";
|
|---|
| 170 |
|
|---|
| 171 | import "./evaluate.js";
|
|---|
| 172 | import "./drop-side-effect-free.js";
|
|---|
| 173 | import "./drop-unused.js";
|
|---|
| 174 | import "./reduce-vars.js";
|
|---|
| 175 | import {
|
|---|
| 176 | is_undeclared_ref,
|
|---|
| 177 | bitwise_binop,
|
|---|
| 178 | lazy_op,
|
|---|
| 179 | is_nullish,
|
|---|
| 180 | is_undefined,
|
|---|
| 181 | is_lhs,
|
|---|
| 182 | aborts,
|
|---|
| 183 | is_used_in_expression,
|
|---|
| 184 | } from "./inference.js";
|
|---|
| 185 | import {
|
|---|
| 186 | SQUEEZED,
|
|---|
| 187 | OPTIMIZED,
|
|---|
| 188 | CLEAR_BETWEEN_PASSES,
|
|---|
| 189 | TOP,
|
|---|
| 190 | UNDEFINED,
|
|---|
| 191 | UNUSED,
|
|---|
| 192 | TRUTHY,
|
|---|
| 193 | FALSY,
|
|---|
| 194 | has_flag,
|
|---|
| 195 | set_flag,
|
|---|
| 196 | clear_flag,
|
|---|
| 197 | } from "./compressor-flags.js";
|
|---|
| 198 | import {
|
|---|
| 199 | make_sequence,
|
|---|
| 200 | best_of,
|
|---|
| 201 | best_of_expression,
|
|---|
| 202 | make_empty_function,
|
|---|
| 203 | make_node_from_constant,
|
|---|
| 204 | merge_sequence,
|
|---|
| 205 | get_simple_key,
|
|---|
| 206 | has_break_or_continue,
|
|---|
| 207 | maintain_this_binding,
|
|---|
| 208 | is_empty,
|
|---|
| 209 | is_identifier_atom,
|
|---|
| 210 | is_reachable,
|
|---|
| 211 | can_be_evicted_from_block,
|
|---|
| 212 | as_statement_array,
|
|---|
| 213 | is_func_expr,
|
|---|
| 214 | } from "./common.js";
|
|---|
| 215 | import { tighten_body, extract_from_unreachable_code } from "./tighten-body.js";
|
|---|
| 216 | import { inline_into_symbolref, inline_into_call } from "./inline.js";
|
|---|
| 217 | import "./global-defs.js";
|
|---|
| 218 | import { is_pure_native_fn, is_pure_native_method, is_pure_native_static_fn, is_pure_native_static_property, pure_access_globals } from "./native-objects.js";
|
|---|
| 219 |
|
|---|
| 220 | class Compressor extends TreeWalker {
|
|---|
| 221 | constructor(options, { false_by_default = false, mangle_options = false }) {
|
|---|
| 222 | super();
|
|---|
| 223 | if (options.defaults !== undefined && !options.defaults) false_by_default = true;
|
|---|
| 224 | this.options = defaults(options, {
|
|---|
| 225 | arguments : false,
|
|---|
| 226 | arrows : !false_by_default,
|
|---|
| 227 | booleans : !false_by_default,
|
|---|
| 228 | booleans_as_integers : false,
|
|---|
| 229 | collapse_vars : !false_by_default,
|
|---|
| 230 | comparisons : !false_by_default,
|
|---|
| 231 | computed_props: !false_by_default,
|
|---|
| 232 | conditionals : !false_by_default,
|
|---|
| 233 | dead_code : !false_by_default,
|
|---|
| 234 | defaults : true,
|
|---|
| 235 | directives : !false_by_default,
|
|---|
| 236 | drop_console : false,
|
|---|
| 237 | drop_debugger : !false_by_default,
|
|---|
| 238 | ecma : 5,
|
|---|
| 239 | builtins_ecma : 5,
|
|---|
| 240 | builtins_pure : false,
|
|---|
| 241 | evaluate : !false_by_default,
|
|---|
| 242 | expression : false,
|
|---|
| 243 | global_defs : false,
|
|---|
| 244 | hoist_funs : false,
|
|---|
| 245 | hoist_props : !false_by_default,
|
|---|
| 246 | hoist_vars : false,
|
|---|
| 247 | ie8 : false,
|
|---|
| 248 | if_return : !false_by_default,
|
|---|
| 249 | inline : !false_by_default,
|
|---|
| 250 | join_vars : !false_by_default,
|
|---|
| 251 | keep_classnames: false,
|
|---|
| 252 | keep_fargs : true,
|
|---|
| 253 | keep_fnames : false,
|
|---|
| 254 | keep_infinity : false,
|
|---|
| 255 | lhs_constants : !false_by_default,
|
|---|
| 256 | loops : !false_by_default,
|
|---|
| 257 | module : false,
|
|---|
| 258 | negate_iife : !false_by_default,
|
|---|
| 259 | passes : 1,
|
|---|
| 260 | properties : !false_by_default,
|
|---|
| 261 | pure_getters : !false_by_default && "strict",
|
|---|
| 262 | pure_funcs : null,
|
|---|
| 263 | pure_new : false,
|
|---|
| 264 | reduce_funcs : !false_by_default,
|
|---|
| 265 | reduce_vars : !false_by_default,
|
|---|
| 266 | sequences : !false_by_default,
|
|---|
| 267 | side_effects : !false_by_default,
|
|---|
| 268 | switches : !false_by_default,
|
|---|
| 269 | top_retain : null,
|
|---|
| 270 | toplevel : !!(options && options["top_retain"]),
|
|---|
| 271 | typeofs : !false_by_default,
|
|---|
| 272 | unsafe : false,
|
|---|
| 273 | unsafe_arrows : false,
|
|---|
| 274 | unsafe_comps : false,
|
|---|
| 275 | unsafe_Function: false,
|
|---|
| 276 | unsafe_math : false,
|
|---|
| 277 | unsafe_symbols: false,
|
|---|
| 278 | unsafe_methods: false,
|
|---|
| 279 | unsafe_proto : false,
|
|---|
| 280 | unsafe_regexp : false,
|
|---|
| 281 | unsafe_undefined: false,
|
|---|
| 282 | unused : !false_by_default,
|
|---|
| 283 | warnings : false // legacy
|
|---|
| 284 | }, true);
|
|---|
| 285 | var global_defs = this.options["global_defs"];
|
|---|
| 286 | if (typeof global_defs == "object") for (var key in global_defs) {
|
|---|
| 287 | if (key[0] === "@" && HOP(global_defs, key)) {
|
|---|
| 288 | global_defs[key.slice(1)] = parse(global_defs[key], {
|
|---|
| 289 | expression: true
|
|---|
| 290 | });
|
|---|
| 291 | }
|
|---|
| 292 | }
|
|---|
| 293 | if (this.options["inline"] === true) this.options["inline"] = 3;
|
|---|
| 294 | var pure_funcs = this.options["pure_funcs"];
|
|---|
| 295 | if (typeof pure_funcs == "function") {
|
|---|
| 296 | this.pure_funcs = pure_funcs;
|
|---|
| 297 | } else {
|
|---|
| 298 | this.pure_funcs = pure_funcs ? function(node) {
|
|---|
| 299 | return !pure_funcs.includes(node.expression.print_to_string());
|
|---|
| 300 | } : return_true;
|
|---|
| 301 | }
|
|---|
| 302 | var top_retain = this.options["top_retain"];
|
|---|
| 303 | if (top_retain instanceof RegExp) {
|
|---|
| 304 | this.top_retain = function(def) {
|
|---|
| 305 | return top_retain.test(def.name);
|
|---|
| 306 | };
|
|---|
| 307 | } else if (typeof top_retain == "function") {
|
|---|
| 308 | this.top_retain = top_retain;
|
|---|
| 309 | } else if (top_retain) {
|
|---|
| 310 | if (typeof top_retain == "string") {
|
|---|
| 311 | top_retain = top_retain.split(/,/);
|
|---|
| 312 | }
|
|---|
| 313 | this.top_retain = function(def) {
|
|---|
| 314 | return top_retain.includes(def.name);
|
|---|
| 315 | };
|
|---|
| 316 | }
|
|---|
| 317 | if (this.options["module"]) {
|
|---|
| 318 | this.directives["use strict"] = true;
|
|---|
| 319 | this.options["toplevel"] = true;
|
|---|
| 320 | }
|
|---|
| 321 | var toplevel = this.options["toplevel"];
|
|---|
| 322 | this.toplevel = typeof toplevel == "string" ? {
|
|---|
| 323 | funcs: /funcs/.test(toplevel),
|
|---|
| 324 | vars: /vars/.test(toplevel)
|
|---|
| 325 | } : {
|
|---|
| 326 | funcs: toplevel,
|
|---|
| 327 | vars: toplevel
|
|---|
| 328 | };
|
|---|
| 329 | var sequences = this.options["sequences"];
|
|---|
| 330 | this.sequences_limit = sequences == 1 ? 800 : sequences | 0;
|
|---|
| 331 | this.evaluated_regexps = new Map();
|
|---|
| 332 | this._toplevel = undefined;
|
|---|
| 333 | this._mangle_options = mangle_options
|
|---|
| 334 | ? format_mangler_options(mangle_options)
|
|---|
| 335 | : mangle_options;
|
|---|
| 336 |
|
|---|
| 337 | this.pure_access_globals = pure_access_globals(this);
|
|---|
| 338 | this.is_pure_native_fn = is_pure_native_fn(this);
|
|---|
| 339 | this.is_pure_native_method = is_pure_native_method(this);
|
|---|
| 340 | this.is_pure_native_static_fn = is_pure_native_static_fn(this);
|
|---|
| 341 | this.is_pure_native_static_property = is_pure_native_static_property(this);
|
|---|
| 342 | }
|
|---|
| 343 |
|
|---|
| 344 | mangle_options() {
|
|---|
| 345 | var nth_identifier = this._mangle_options && this._mangle_options.nth_identifier || base54;
|
|---|
| 346 | var module = this._mangle_options && this._mangle_options.module || this.option("module");
|
|---|
| 347 | return { ie8: this.option("ie8"), nth_identifier, module };
|
|---|
| 348 | }
|
|---|
| 349 |
|
|---|
| 350 | option(key) {
|
|---|
| 351 | return this.options[key];
|
|---|
| 352 | }
|
|---|
| 353 |
|
|---|
| 354 | exposed(def) {
|
|---|
| 355 | if (def.export) return true;
|
|---|
| 356 | if (def.global) for (var i = 0, len = def.orig.length; i < len; i++)
|
|---|
| 357 | if (!this.toplevel[def.orig[i] instanceof AST_SymbolDefun ? "funcs" : "vars"])
|
|---|
| 358 | return true;
|
|---|
| 359 | return false;
|
|---|
| 360 | }
|
|---|
| 361 |
|
|---|
| 362 | in_boolean_context() {
|
|---|
| 363 | if (!this.option("booleans")) return false;
|
|---|
| 364 | var self = this.self();
|
|---|
| 365 | for (var i = 0, p; p = this.parent(i); i++) {
|
|---|
| 366 | if (p instanceof AST_SimpleStatement
|
|---|
| 367 | || p instanceof AST_Conditional && p.condition === self
|
|---|
| 368 | || p instanceof AST_DWLoop && p.condition === self
|
|---|
| 369 | || p instanceof AST_For && p.condition === self
|
|---|
| 370 | || p instanceof AST_If && p.condition === self
|
|---|
| 371 | || p instanceof AST_UnaryPrefix && p.operator == "!" && p.expression === self) {
|
|---|
| 372 | return true;
|
|---|
| 373 | }
|
|---|
| 374 | if (
|
|---|
| 375 | p instanceof AST_Binary
|
|---|
| 376 | && (
|
|---|
| 377 | p.operator == "&&"
|
|---|
| 378 | || p.operator == "||"
|
|---|
| 379 | || p.operator == "??"
|
|---|
| 380 | )
|
|---|
| 381 | || p instanceof AST_Conditional
|
|---|
| 382 | || p.tail_node() === self
|
|---|
| 383 | ) {
|
|---|
| 384 | self = p;
|
|---|
| 385 | } else {
|
|---|
| 386 | return false;
|
|---|
| 387 | }
|
|---|
| 388 | }
|
|---|
| 389 | }
|
|---|
| 390 |
|
|---|
| 391 | /** True if compressor.self()'s result will be turned into a 32-bit integer.
|
|---|
| 392 | * ex:
|
|---|
| 393 | * ~{expr}
|
|---|
| 394 | * (1, 2, {expr}) | 0
|
|---|
| 395 | **/
|
|---|
| 396 | in_32_bit_context(other_operand_must_be_number) {
|
|---|
| 397 | if (!this.option("evaluate")) return false;
|
|---|
| 398 | var self = this.self();
|
|---|
| 399 | for (var i = 0, p; p = this.parent(i); i++) {
|
|---|
| 400 | if (p instanceof AST_Binary && bitwise_binop.has(p.operator)) {
|
|---|
| 401 | if (other_operand_must_be_number) {
|
|---|
| 402 | return (self === p.left ? p.right : p.left).is_number(this);
|
|---|
| 403 | } else {
|
|---|
| 404 | return true;
|
|---|
| 405 | }
|
|---|
| 406 | }
|
|---|
| 407 | if (p instanceof AST_UnaryPrefix) {
|
|---|
| 408 | return p.operator === "~";
|
|---|
| 409 | }
|
|---|
| 410 | if (
|
|---|
| 411 | p instanceof AST_Binary
|
|---|
| 412 | && (
|
|---|
| 413 | // Don't talk about p.left. Can change branch taken
|
|---|
| 414 | p.operator == "&&" && p.right === self
|
|---|
| 415 | || p.operator == "||" && p.right === self
|
|---|
| 416 | || p.operator == "??" && p.right === self
|
|---|
| 417 | )
|
|---|
| 418 | || p instanceof AST_Conditional && p.condition !== self
|
|---|
| 419 | || p.tail_node() === self
|
|---|
| 420 | ) {
|
|---|
| 421 | self = p;
|
|---|
| 422 | } else {
|
|---|
| 423 | return false;
|
|---|
| 424 | }
|
|---|
| 425 | }
|
|---|
| 426 | }
|
|---|
| 427 |
|
|---|
| 428 | in_computed_key() {
|
|---|
| 429 | if (!this.option("evaluate")) return false;
|
|---|
| 430 | var self = this.self();
|
|---|
| 431 | for (var i = 0, p; p = this.parent(i); i++) {
|
|---|
| 432 | if (p instanceof AST_ObjectProperty && p.key === self) {
|
|---|
| 433 | return true;
|
|---|
| 434 | }
|
|---|
| 435 | }
|
|---|
| 436 | return false;
|
|---|
| 437 | }
|
|---|
| 438 |
|
|---|
| 439 | get_toplevel() {
|
|---|
| 440 | return this._toplevel;
|
|---|
| 441 | }
|
|---|
| 442 |
|
|---|
| 443 | compress(toplevel) {
|
|---|
| 444 | toplevel = toplevel.resolve_defines(this);
|
|---|
| 445 | this._toplevel = toplevel;
|
|---|
| 446 | if (this.option("expression")) {
|
|---|
| 447 | this._toplevel.process_expression(true);
|
|---|
| 448 | }
|
|---|
| 449 | var passes = +this.options.passes || 1;
|
|---|
| 450 | var min_count = 1 / 0;
|
|---|
| 451 | var stopping = false;
|
|---|
| 452 | var mangle = this.mangle_options();
|
|---|
| 453 | for (var pass = 0; pass < passes; pass++) {
|
|---|
| 454 | this._toplevel.figure_out_scope(mangle);
|
|---|
| 455 | if (pass === 0 && this.option("drop_console")) {
|
|---|
| 456 | // must be run before reduce_vars and compress pass
|
|---|
| 457 | this._toplevel = this._toplevel.drop_console(this.option("drop_console"));
|
|---|
| 458 | }
|
|---|
| 459 | if (pass > 0 || this.option("reduce_vars")) {
|
|---|
| 460 | this._toplevel.reset_opt_flags(this);
|
|---|
| 461 | }
|
|---|
| 462 | this._toplevel = this._toplevel.transform(this);
|
|---|
| 463 | if (passes > 1) {
|
|---|
| 464 | let count = 0;
|
|---|
| 465 | walk(this._toplevel, () => { count++; });
|
|---|
| 466 | if (count < min_count) {
|
|---|
| 467 | min_count = count;
|
|---|
| 468 | stopping = false;
|
|---|
| 469 | } else if (stopping) {
|
|---|
| 470 | break;
|
|---|
| 471 | } else {
|
|---|
| 472 | stopping = true;
|
|---|
| 473 | }
|
|---|
| 474 | }
|
|---|
| 475 | }
|
|---|
| 476 | if (this.option("expression")) {
|
|---|
| 477 | this._toplevel.process_expression(false);
|
|---|
| 478 | }
|
|---|
| 479 | toplevel = this._toplevel;
|
|---|
| 480 | this._toplevel = undefined;
|
|---|
| 481 | return toplevel;
|
|---|
| 482 | }
|
|---|
| 483 |
|
|---|
| 484 | before(node, descend) {
|
|---|
| 485 | if (has_flag(node, SQUEEZED)) return node;
|
|---|
| 486 | var was_scope = false;
|
|---|
| 487 | if (node instanceof AST_Scope) {
|
|---|
| 488 | node = node.hoist_properties(this);
|
|---|
| 489 | node = node.hoist_declarations(this);
|
|---|
| 490 | was_scope = true;
|
|---|
| 491 | }
|
|---|
| 492 | // Before https://github.com/mishoo/UglifyJS2/pull/1602 AST_Node.optimize()
|
|---|
| 493 | // would call AST_Node.transform() if a different instance of AST_Node is
|
|---|
| 494 | // produced after def_optimize().
|
|---|
| 495 | // This corrupts TreeWalker.stack, which cause AST look-ups to malfunction.
|
|---|
| 496 | // Migrate and defer all children's AST_Node.transform() to below, which
|
|---|
| 497 | // will now happen after this parent AST_Node has been properly substituted
|
|---|
| 498 | // thus gives a consistent AST snapshot.
|
|---|
| 499 | descend(node, this);
|
|---|
| 500 | // Existing code relies on how AST_Node.optimize() worked, and omitting the
|
|---|
| 501 | // following replacement call would result in degraded efficiency of both
|
|---|
| 502 | // output and performance.
|
|---|
| 503 | descend(node, this);
|
|---|
| 504 | var opt = node.optimize(this);
|
|---|
| 505 | if (was_scope && opt instanceof AST_Scope) {
|
|---|
| 506 | opt.drop_unused(this);
|
|---|
| 507 | descend(opt, this);
|
|---|
| 508 | }
|
|---|
| 509 | if (opt === node) set_flag(opt, SQUEEZED);
|
|---|
| 510 | return opt;
|
|---|
| 511 | }
|
|---|
| 512 |
|
|---|
| 513 | /** Alternative to plain is_lhs() which doesn't work within .optimize() */
|
|---|
| 514 | is_lhs() {
|
|---|
| 515 | const self = this.stack[this.stack.length - 1];
|
|---|
| 516 | const parent = this.stack[this.stack.length - 2];
|
|---|
| 517 | return is_lhs(self, parent);
|
|---|
| 518 | }
|
|---|
| 519 | }
|
|---|
| 520 |
|
|---|
| 521 |
|
|---|
| 522 | function def_optimize(node, optimizer) {
|
|---|
| 523 | node.DEFMETHOD("optimize", function(compressor) {
|
|---|
| 524 | var self = this;
|
|---|
| 525 | if (has_flag(self, OPTIMIZED)) return self;
|
|---|
| 526 | if (compressor.has_directive("use asm")) return self;
|
|---|
| 527 | var opt = optimizer(self, compressor);
|
|---|
| 528 | set_flag(opt, OPTIMIZED);
|
|---|
| 529 | return opt;
|
|---|
| 530 | });
|
|---|
| 531 | }
|
|---|
| 532 |
|
|---|
| 533 | def_optimize(AST_Node, function(self) {
|
|---|
| 534 | return self;
|
|---|
| 535 | });
|
|---|
| 536 |
|
|---|
| 537 | AST_Toplevel.DEFMETHOD("drop_console", function(options) {
|
|---|
| 538 | const isArray = Array.isArray(options);
|
|---|
| 539 | const tt = new TreeTransformer(function(self) {
|
|---|
| 540 | if (self.TYPE !== "Call") {
|
|---|
| 541 | return;
|
|---|
| 542 | }
|
|---|
| 543 |
|
|---|
| 544 | var exp = self.expression;
|
|---|
| 545 |
|
|---|
| 546 | if (!(exp instanceof AST_PropAccess)) {
|
|---|
| 547 | return;
|
|---|
| 548 | }
|
|---|
| 549 |
|
|---|
| 550 | var name = exp.expression;
|
|---|
| 551 | var property = exp.property;
|
|---|
| 552 | var depth = 2;
|
|---|
| 553 | while (name.expression) {
|
|---|
| 554 | property = name.property;
|
|---|
| 555 | name = name.expression;
|
|---|
| 556 | depth++;
|
|---|
| 557 | }
|
|---|
| 558 |
|
|---|
| 559 | if (isArray && !options.includes(property)) {
|
|---|
| 560 | return;
|
|---|
| 561 | }
|
|---|
| 562 |
|
|---|
| 563 | if (is_undeclared_ref(name) && name.name == "console") {
|
|---|
| 564 | if (
|
|---|
| 565 | depth === 3
|
|---|
| 566 | && !["call", "apply"].includes(exp.property)
|
|---|
| 567 | && is_used_in_expression(tt)
|
|---|
| 568 | ) {
|
|---|
| 569 | // a (used) call to Function.prototype methods (eg: console.log.bind(console))
|
|---|
| 570 | // but not .call and .apply which would also return undefined.
|
|---|
| 571 | exp.expression = make_empty_function(self);
|
|---|
| 572 | set_flag(exp.expression, SQUEEZED);
|
|---|
| 573 | self.args = [];
|
|---|
| 574 | } else {
|
|---|
| 575 | return make_void_0(self);
|
|---|
| 576 | }
|
|---|
| 577 | }
|
|---|
| 578 | });
|
|---|
| 579 |
|
|---|
| 580 | return this.transform(tt);
|
|---|
| 581 | });
|
|---|
| 582 |
|
|---|
| 583 | AST_Node.DEFMETHOD("equivalent_to", function(node) {
|
|---|
| 584 | return equivalent_to(this, node);
|
|---|
| 585 | });
|
|---|
| 586 |
|
|---|
| 587 | AST_Scope.DEFMETHOD("process_expression", function(insert, compressor) {
|
|---|
| 588 | var self = this;
|
|---|
| 589 | var tt = new TreeTransformer(function(node) {
|
|---|
| 590 | if (insert && node instanceof AST_SimpleStatement) {
|
|---|
| 591 | return make_node(AST_Return, node, {
|
|---|
| 592 | value: node.body
|
|---|
| 593 | });
|
|---|
| 594 | }
|
|---|
| 595 | if (!insert && node instanceof AST_Return) {
|
|---|
| 596 | if (compressor) {
|
|---|
| 597 | var value = node.value && node.value.drop_side_effect_free(compressor, true);
|
|---|
| 598 | return value
|
|---|
| 599 | ? make_node(AST_SimpleStatement, node, { body: value })
|
|---|
| 600 | : make_node(AST_EmptyStatement, node);
|
|---|
| 601 | }
|
|---|
| 602 | return make_node(AST_SimpleStatement, node, {
|
|---|
| 603 | body: node.value || make_void_0(node)
|
|---|
| 604 | });
|
|---|
| 605 | }
|
|---|
| 606 | if (node instanceof AST_Class || node instanceof AST_Lambda && node !== self) {
|
|---|
| 607 | return node;
|
|---|
| 608 | }
|
|---|
| 609 | if (node instanceof AST_Block) {
|
|---|
| 610 | var index = node.body.length - 1;
|
|---|
| 611 | if (index >= 0) {
|
|---|
| 612 | node.body[index] = node.body[index].transform(tt);
|
|---|
| 613 | }
|
|---|
| 614 | } else if (node instanceof AST_If) {
|
|---|
| 615 | node.body = node.body.transform(tt);
|
|---|
| 616 | if (node.alternative) {
|
|---|
| 617 | node.alternative = node.alternative.transform(tt);
|
|---|
| 618 | }
|
|---|
| 619 | } else if (node instanceof AST_With) {
|
|---|
| 620 | node.body = node.body.transform(tt);
|
|---|
| 621 | }
|
|---|
| 622 | return node;
|
|---|
| 623 | });
|
|---|
| 624 | self.transform(tt);
|
|---|
| 625 | });
|
|---|
| 626 |
|
|---|
| 627 | AST_Toplevel.DEFMETHOD("reset_opt_flags", function(compressor) {
|
|---|
| 628 | const self = this;
|
|---|
| 629 | const reduce_vars = compressor.option("reduce_vars");
|
|---|
| 630 |
|
|---|
| 631 | const preparation = new TreeWalker(function(node, descend) {
|
|---|
| 632 | clear_flag(node, CLEAR_BETWEEN_PASSES);
|
|---|
| 633 | if (reduce_vars) {
|
|---|
| 634 | if (compressor.top_retain
|
|---|
| 635 | && node instanceof AST_Defun // Only functions are retained
|
|---|
| 636 | && preparation.parent() === self
|
|---|
| 637 | ) {
|
|---|
| 638 | set_flag(node, TOP);
|
|---|
| 639 | }
|
|---|
| 640 | return node.reduce_vars(preparation, descend, compressor);
|
|---|
| 641 | }
|
|---|
| 642 | });
|
|---|
| 643 | // Stack of look-up tables to keep track of whether a `SymbolDef` has been
|
|---|
| 644 | // properly assigned before use:
|
|---|
| 645 | // - `push()` & `pop()` when visiting conditional branches
|
|---|
| 646 | preparation.safe_ids = Object.create(null);
|
|---|
| 647 | preparation.in_loop = null;
|
|---|
| 648 | preparation.loop_ids = new Map();
|
|---|
| 649 | preparation.defs_to_safe_ids = new Map();
|
|---|
| 650 | self.walk(preparation);
|
|---|
| 651 | });
|
|---|
| 652 |
|
|---|
| 653 | AST_Symbol.DEFMETHOD("fixed_value", function() {
|
|---|
| 654 | var fixed = this.thedef.fixed;
|
|---|
| 655 | if (!fixed || fixed instanceof AST_Node) return fixed;
|
|---|
| 656 | return fixed();
|
|---|
| 657 | });
|
|---|
| 658 |
|
|---|
| 659 | AST_SymbolRef.DEFMETHOD("is_immutable", function() {
|
|---|
| 660 | var orig = this.definition().orig;
|
|---|
| 661 | return orig.length == 1 && orig[0] instanceof AST_SymbolLambda;
|
|---|
| 662 | });
|
|---|
| 663 |
|
|---|
| 664 | function find_variable(compressor, name) {
|
|---|
| 665 | var scope, i = 0;
|
|---|
| 666 | while (scope = compressor.parent(i++)) {
|
|---|
| 667 | if (scope instanceof AST_Scope) break;
|
|---|
| 668 | if (scope instanceof AST_Catch && scope.argname) {
|
|---|
| 669 | scope = scope.argname.definition().scope;
|
|---|
| 670 | break;
|
|---|
| 671 | }
|
|---|
| 672 | }
|
|---|
| 673 | return scope.find_variable(name);
|
|---|
| 674 | }
|
|---|
| 675 |
|
|---|
| 676 | AST_SymbolRef.DEFMETHOD("is_declared", function(compressor) {
|
|---|
| 677 | return !this.definition().undeclared
|
|---|
| 678 | || (compressor.option("unsafe") || compressor.option("builtins_pure")) && compressor.pure_access_globals(this.name);
|
|---|
| 679 | });
|
|---|
| 680 |
|
|---|
| 681 | /* -----[ optimizers ]----- */
|
|---|
| 682 |
|
|---|
| 683 | var directives = new Set(["use asm", "use strict"]);
|
|---|
| 684 | def_optimize(AST_Directive, function(self, compressor) {
|
|---|
| 685 | if (compressor.option("directives")
|
|---|
| 686 | && (!directives.has(self.value) || compressor.has_directive(self.value) !== self)) {
|
|---|
| 687 | return make_node(AST_EmptyStatement, self);
|
|---|
| 688 | }
|
|---|
| 689 | return self;
|
|---|
| 690 | });
|
|---|
| 691 |
|
|---|
| 692 | def_optimize(AST_Debugger, function(self, compressor) {
|
|---|
| 693 | if (compressor.option("drop_debugger"))
|
|---|
| 694 | return make_node(AST_EmptyStatement, self);
|
|---|
| 695 | return self;
|
|---|
| 696 | });
|
|---|
| 697 |
|
|---|
| 698 | def_optimize(AST_LabeledStatement, function(self, compressor) {
|
|---|
| 699 | if (self.body instanceof AST_Break
|
|---|
| 700 | && compressor.loopcontrol_target(self.body) === self.body) {
|
|---|
| 701 | return make_node(AST_EmptyStatement, self);
|
|---|
| 702 | }
|
|---|
| 703 | return self.label.references.length == 0 ? self.body : self;
|
|---|
| 704 | });
|
|---|
| 705 |
|
|---|
| 706 | def_optimize(AST_Block, function(self, compressor) {
|
|---|
| 707 | tighten_body(self.body, compressor);
|
|---|
| 708 | return self;
|
|---|
| 709 | });
|
|---|
| 710 |
|
|---|
| 711 | function can_be_extracted_from_if_block(node) {
|
|---|
| 712 | return !(
|
|---|
| 713 | node instanceof AST_Const
|
|---|
| 714 | || node instanceof AST_Let
|
|---|
| 715 | || node instanceof AST_Using
|
|---|
| 716 | || node instanceof AST_Class
|
|---|
| 717 | );
|
|---|
| 718 | }
|
|---|
| 719 |
|
|---|
| 720 | def_optimize(AST_BlockStatement, function(self, compressor) {
|
|---|
| 721 | tighten_body(self.body, compressor);
|
|---|
| 722 | switch (self.body.length) {
|
|---|
| 723 | case 1:
|
|---|
| 724 | if (!compressor.has_directive("use strict")
|
|---|
| 725 | && compressor.parent() instanceof AST_If
|
|---|
| 726 | && can_be_extracted_from_if_block(self.body[0])
|
|---|
| 727 | || can_be_evicted_from_block(self.body[0])) {
|
|---|
| 728 | return self.body[0];
|
|---|
| 729 | }
|
|---|
| 730 | break;
|
|---|
| 731 | case 0: return make_node(AST_EmptyStatement, self);
|
|---|
| 732 | }
|
|---|
| 733 | return self;
|
|---|
| 734 | });
|
|---|
| 735 |
|
|---|
| 736 | function opt_AST_Lambda(self, compressor) {
|
|---|
| 737 | tighten_body(self.body, compressor);
|
|---|
| 738 | if (compressor.option("side_effects")
|
|---|
| 739 | && self.body.length == 1
|
|---|
| 740 | && self.body[0] === compressor.has_directive("use strict")) {
|
|---|
| 741 | self.body.length = 0;
|
|---|
| 742 | }
|
|---|
| 743 | return self;
|
|---|
| 744 | }
|
|---|
| 745 | def_optimize(AST_Lambda, opt_AST_Lambda);
|
|---|
| 746 |
|
|---|
| 747 | AST_Scope.DEFMETHOD("hoist_declarations", function(compressor) {
|
|---|
| 748 | var self = this;
|
|---|
| 749 | if (compressor.has_directive("use asm")) return self;
|
|---|
| 750 |
|
|---|
| 751 | var hoist_funs = compressor.option("hoist_funs");
|
|---|
| 752 | var hoist_vars = compressor.option("hoist_vars");
|
|---|
| 753 |
|
|---|
| 754 | if (hoist_funs || hoist_vars) {
|
|---|
| 755 | var dirs = [];
|
|---|
| 756 | var hoisted = [];
|
|---|
| 757 | var vars = new Map(), vars_found = 0, var_decl = 0;
|
|---|
| 758 | // let's count var_decl first, we seem to waste a lot of
|
|---|
| 759 | // space if we hoist `var` when there's only one.
|
|---|
| 760 | walk(self, node => {
|
|---|
| 761 | if (node instanceof AST_Scope && node !== self)
|
|---|
| 762 | return true;
|
|---|
| 763 | if (node instanceof AST_Var) {
|
|---|
| 764 | ++var_decl;
|
|---|
| 765 | return true;
|
|---|
| 766 | }
|
|---|
| 767 | });
|
|---|
| 768 | hoist_vars = hoist_vars && var_decl > 1;
|
|---|
| 769 | var tt = new TreeTransformer(
|
|---|
| 770 | function before(node) {
|
|---|
| 771 | if (node !== self) {
|
|---|
| 772 | if (node instanceof AST_Directive) {
|
|---|
| 773 | dirs.push(node);
|
|---|
| 774 | return make_node(AST_EmptyStatement, node);
|
|---|
| 775 | }
|
|---|
| 776 | if (hoist_funs && node instanceof AST_Defun
|
|---|
| 777 | && !(tt.parent() instanceof AST_Export)
|
|---|
| 778 | && tt.parent() === self) {
|
|---|
| 779 | hoisted.push(node);
|
|---|
| 780 | return make_node(AST_EmptyStatement, node);
|
|---|
| 781 | }
|
|---|
| 782 | if (
|
|---|
| 783 | hoist_vars
|
|---|
| 784 | && node instanceof AST_Var
|
|---|
| 785 | && !node.definitions.some(def => def.name instanceof AST_Destructuring)
|
|---|
| 786 | ) {
|
|---|
| 787 | node.definitions.forEach(function(def) {
|
|---|
| 788 | vars.set(def.name.name, def);
|
|---|
| 789 | ++vars_found;
|
|---|
| 790 | });
|
|---|
| 791 | var seq = node.to_assignments(compressor);
|
|---|
| 792 | var p = tt.parent();
|
|---|
| 793 | if (p instanceof AST_ForIn && p.init === node) {
|
|---|
| 794 | if (seq == null) {
|
|---|
| 795 | var def = node.definitions[0].name;
|
|---|
| 796 | return make_node(AST_SymbolRef, def, def);
|
|---|
| 797 | }
|
|---|
| 798 | return seq;
|
|---|
| 799 | }
|
|---|
| 800 | if (p instanceof AST_For && p.init === node) {
|
|---|
| 801 | return seq;
|
|---|
| 802 | }
|
|---|
| 803 | if (!seq) return make_node(AST_EmptyStatement, node);
|
|---|
| 804 | return make_node(AST_SimpleStatement, node, {
|
|---|
| 805 | body: seq
|
|---|
| 806 | });
|
|---|
| 807 | }
|
|---|
| 808 | if (node instanceof AST_Scope)
|
|---|
| 809 | return node; // to avoid descending in nested scopes
|
|---|
| 810 | }
|
|---|
| 811 | }
|
|---|
| 812 | );
|
|---|
| 813 | self = self.transform(tt);
|
|---|
| 814 | if (vars_found > 0) {
|
|---|
| 815 | // collect only vars which don't show up in self's arguments list
|
|---|
| 816 | var defs = [];
|
|---|
| 817 | const is_lambda = self instanceof AST_Lambda;
|
|---|
| 818 | const args_as_names = is_lambda ? self.args_as_names() : null;
|
|---|
| 819 | vars.forEach((def, name) => {
|
|---|
| 820 | if (is_lambda && args_as_names.some((x) => x.name === def.name.name)) {
|
|---|
| 821 | vars.delete(name);
|
|---|
| 822 | } else {
|
|---|
| 823 | def = def.clone();
|
|---|
| 824 | def.value = null;
|
|---|
| 825 | defs.push(def);
|
|---|
| 826 | vars.set(name, def);
|
|---|
| 827 | }
|
|---|
| 828 | });
|
|---|
| 829 | if (defs.length > 0) {
|
|---|
| 830 | // try to merge in assignments
|
|---|
| 831 | for (var i = 0; i < self.body.length;) {
|
|---|
| 832 | if (self.body[i] instanceof AST_SimpleStatement) {
|
|---|
| 833 | var expr = self.body[i].body, sym, assign;
|
|---|
| 834 | if (expr instanceof AST_Assign
|
|---|
| 835 | && expr.operator == "="
|
|---|
| 836 | && (sym = expr.left) instanceof AST_Symbol
|
|---|
| 837 | && vars.has(sym.name)
|
|---|
| 838 | ) {
|
|---|
| 839 | var def = vars.get(sym.name);
|
|---|
| 840 | if (def.value) break;
|
|---|
| 841 | def.value = expr.right;
|
|---|
| 842 | remove(defs, def);
|
|---|
| 843 | defs.push(def);
|
|---|
| 844 | self.body.splice(i, 1);
|
|---|
| 845 | continue;
|
|---|
| 846 | }
|
|---|
| 847 | if (expr instanceof AST_Sequence
|
|---|
| 848 | && (assign = expr.expressions[0]) instanceof AST_Assign
|
|---|
| 849 | && assign.operator == "="
|
|---|
| 850 | && (sym = assign.left) instanceof AST_Symbol
|
|---|
| 851 | && vars.has(sym.name)
|
|---|
| 852 | ) {
|
|---|
| 853 | var def = vars.get(sym.name);
|
|---|
| 854 | if (def.value) break;
|
|---|
| 855 | def.value = assign.right;
|
|---|
| 856 | remove(defs, def);
|
|---|
| 857 | defs.push(def);
|
|---|
| 858 | self.body[i].body = make_sequence(expr, expr.expressions.slice(1));
|
|---|
| 859 | continue;
|
|---|
| 860 | }
|
|---|
| 861 | }
|
|---|
| 862 | if (self.body[i] instanceof AST_EmptyStatement) {
|
|---|
| 863 | self.body.splice(i, 1);
|
|---|
| 864 | continue;
|
|---|
| 865 | }
|
|---|
| 866 | if (self.body[i] instanceof AST_BlockStatement) {
|
|---|
| 867 | self.body.splice(i, 1, ...self.body[i].body);
|
|---|
| 868 | continue;
|
|---|
| 869 | }
|
|---|
| 870 | break;
|
|---|
| 871 | }
|
|---|
| 872 | defs = make_node(AST_Var, self, {
|
|---|
| 873 | definitions: defs
|
|---|
| 874 | });
|
|---|
| 875 | hoisted.push(defs);
|
|---|
| 876 | }
|
|---|
| 877 | }
|
|---|
| 878 | self.body = dirs.concat(hoisted, self.body);
|
|---|
| 879 | }
|
|---|
| 880 | return self;
|
|---|
| 881 | });
|
|---|
| 882 |
|
|---|
| 883 | AST_Scope.DEFMETHOD("hoist_properties", function(compressor) {
|
|---|
| 884 | var self = this;
|
|---|
| 885 | if (!compressor.option("hoist_props") || compressor.has_directive("use asm")) return self;
|
|---|
| 886 | var top_retain = self instanceof AST_Toplevel && compressor.top_retain || return_false;
|
|---|
| 887 | var defs_by_id = new Map();
|
|---|
| 888 | var hoister = new TreeTransformer(function(node, descend) {
|
|---|
| 889 | if (node instanceof AST_VarDef) {
|
|---|
| 890 | const sym = node.name;
|
|---|
| 891 | let def;
|
|---|
| 892 | let value;
|
|---|
| 893 | if (sym.scope === self
|
|---|
| 894 | && !(sym instanceof AST_SymbolUsing)
|
|---|
| 895 | && (def = sym.definition()).escaped != 1
|
|---|
| 896 | && !def.assignments
|
|---|
| 897 | && !def.direct_access
|
|---|
| 898 | && !def.single_use
|
|---|
| 899 | && !compressor.exposed(def)
|
|---|
| 900 | && !top_retain(def)
|
|---|
| 901 | && (value = sym.fixed_value()) === node.value
|
|---|
| 902 | && value instanceof AST_Object
|
|---|
| 903 | && !value.properties.some(prop =>
|
|---|
| 904 | prop instanceof AST_Expansion || prop.computed_key()
|
|---|
| 905 | )
|
|---|
| 906 | ) {
|
|---|
| 907 | descend(node, this);
|
|---|
| 908 | const defs = new Map();
|
|---|
| 909 | const assignments = [];
|
|---|
| 910 | value.properties.forEach(({ key, value }) => {
|
|---|
| 911 | const scope = hoister.find_scope();
|
|---|
| 912 | const symbol = self.create_symbol(sym.CTOR, {
|
|---|
| 913 | source: sym,
|
|---|
| 914 | scope,
|
|---|
| 915 | conflict_scopes: new Set([
|
|---|
| 916 | scope,
|
|---|
| 917 | ...sym.definition().references.map(ref => ref.scope)
|
|---|
| 918 | ]),
|
|---|
| 919 | tentative_name: sym.name + "_" + key
|
|---|
| 920 | });
|
|---|
| 921 |
|
|---|
| 922 | defs.set(String(key), symbol.definition());
|
|---|
| 923 |
|
|---|
| 924 | assignments.push(make_node(AST_VarDef, node, {
|
|---|
| 925 | name: symbol,
|
|---|
| 926 | value
|
|---|
| 927 | }));
|
|---|
| 928 | });
|
|---|
| 929 | defs_by_id.set(def.id, defs);
|
|---|
| 930 | return MAP.splice(assignments);
|
|---|
| 931 | }
|
|---|
| 932 | } else if (node instanceof AST_PropAccess
|
|---|
| 933 | && node.expression instanceof AST_SymbolRef
|
|---|
| 934 | ) {
|
|---|
| 935 | const defs = defs_by_id.get(node.expression.definition().id);
|
|---|
| 936 | if (defs) {
|
|---|
| 937 | const def = defs.get(String(get_simple_key(node.property)));
|
|---|
| 938 | const sym = make_node(AST_SymbolRef, node, {
|
|---|
| 939 | name: def.name,
|
|---|
| 940 | scope: node.expression.scope,
|
|---|
| 941 | thedef: def
|
|---|
| 942 | });
|
|---|
| 943 | sym.reference({});
|
|---|
| 944 | return sym;
|
|---|
| 945 | }
|
|---|
| 946 | }
|
|---|
| 947 | });
|
|---|
| 948 | return self.transform(hoister);
|
|---|
| 949 | });
|
|---|
| 950 |
|
|---|
| 951 | def_optimize(AST_SimpleStatement, function(self, compressor) {
|
|---|
| 952 | if (compressor.option("side_effects")) {
|
|---|
| 953 | var body = self.body;
|
|---|
| 954 | var node = body.drop_side_effect_free(compressor, true);
|
|---|
| 955 | if (!node) {
|
|---|
| 956 | return make_node(AST_EmptyStatement, self);
|
|---|
| 957 | }
|
|---|
| 958 | if (node !== body) {
|
|---|
| 959 | return make_node(AST_SimpleStatement, self, { body: node });
|
|---|
| 960 | }
|
|---|
| 961 | }
|
|---|
| 962 | return self;
|
|---|
| 963 | });
|
|---|
| 964 |
|
|---|
| 965 | def_optimize(AST_While, function(self, compressor) {
|
|---|
| 966 | return compressor.option("loops") ? make_node(AST_For, self, self).optimize(compressor) : self;
|
|---|
| 967 | });
|
|---|
| 968 |
|
|---|
| 969 | def_optimize(AST_Do, function(self, compressor) {
|
|---|
| 970 | if (!compressor.option("loops")) return self;
|
|---|
| 971 | var cond = self.condition.tail_node().evaluate(compressor);
|
|---|
| 972 | if (!(cond instanceof AST_Node)) {
|
|---|
| 973 | if (cond) return make_node(AST_For, self, {
|
|---|
| 974 | body: make_node(AST_BlockStatement, self.body, {
|
|---|
| 975 | body: [
|
|---|
| 976 | self.body,
|
|---|
| 977 | make_node(AST_SimpleStatement, self.condition, {
|
|---|
| 978 | body: self.condition
|
|---|
| 979 | })
|
|---|
| 980 | ]
|
|---|
| 981 | })
|
|---|
| 982 | }).optimize(compressor);
|
|---|
| 983 | if (!has_break_or_continue(self, compressor.parent())) {
|
|---|
| 984 | return make_node(AST_BlockStatement, self.body, {
|
|---|
| 985 | body: [
|
|---|
| 986 | self.body,
|
|---|
| 987 | make_node(AST_SimpleStatement, self.condition, {
|
|---|
| 988 | body: self.condition
|
|---|
| 989 | })
|
|---|
| 990 | ]
|
|---|
| 991 | }).optimize(compressor);
|
|---|
| 992 | }
|
|---|
| 993 | }
|
|---|
| 994 | return self;
|
|---|
| 995 | });
|
|---|
| 996 |
|
|---|
| 997 | function if_break_in_loop(self, compressor) {
|
|---|
| 998 | var first = self.body instanceof AST_BlockStatement ? self.body.body[0] : self.body;
|
|---|
| 999 | if (compressor.option("dead_code") && is_break(first)) {
|
|---|
| 1000 | var body = [];
|
|---|
| 1001 | if (self.init instanceof AST_Statement) {
|
|---|
| 1002 | body.push(self.init);
|
|---|
| 1003 | } else if (self.init) {
|
|---|
| 1004 | body.push(make_node(AST_SimpleStatement, self.init, {
|
|---|
| 1005 | body: self.init
|
|---|
| 1006 | }));
|
|---|
| 1007 | }
|
|---|
| 1008 | if (self.condition) {
|
|---|
| 1009 | body.push(make_node(AST_SimpleStatement, self.condition, {
|
|---|
| 1010 | body: self.condition
|
|---|
| 1011 | }));
|
|---|
| 1012 | }
|
|---|
| 1013 | extract_from_unreachable_code(compressor, self.body, body);
|
|---|
| 1014 | return make_node(AST_BlockStatement, self, {
|
|---|
| 1015 | body: body
|
|---|
| 1016 | });
|
|---|
| 1017 | }
|
|---|
| 1018 | if (first instanceof AST_If) {
|
|---|
| 1019 | if (is_break(first.body)) {
|
|---|
| 1020 | if (self.condition) {
|
|---|
| 1021 | self.condition = make_node(AST_Binary, self.condition, {
|
|---|
| 1022 | left: self.condition,
|
|---|
| 1023 | operator: "&&",
|
|---|
| 1024 | right: first.condition.negate(compressor),
|
|---|
| 1025 | });
|
|---|
| 1026 | } else {
|
|---|
| 1027 | self.condition = first.condition.negate(compressor);
|
|---|
| 1028 | }
|
|---|
| 1029 | drop_it(first.alternative);
|
|---|
| 1030 | } else if (is_break(first.alternative)) {
|
|---|
| 1031 | if (self.condition) {
|
|---|
| 1032 | self.condition = make_node(AST_Binary, self.condition, {
|
|---|
| 1033 | left: self.condition,
|
|---|
| 1034 | operator: "&&",
|
|---|
| 1035 | right: first.condition,
|
|---|
| 1036 | });
|
|---|
| 1037 | } else {
|
|---|
| 1038 | self.condition = first.condition;
|
|---|
| 1039 | }
|
|---|
| 1040 | drop_it(first.body);
|
|---|
| 1041 | }
|
|---|
| 1042 | }
|
|---|
| 1043 | return self;
|
|---|
| 1044 |
|
|---|
| 1045 | function is_break(node) {
|
|---|
| 1046 | return node instanceof AST_Break
|
|---|
| 1047 | && compressor.loopcontrol_target(node) === compressor.self();
|
|---|
| 1048 | }
|
|---|
| 1049 |
|
|---|
| 1050 | function drop_it(rest) {
|
|---|
| 1051 | rest = as_statement_array(rest);
|
|---|
| 1052 | if (self.body instanceof AST_BlockStatement) {
|
|---|
| 1053 | self.body = self.body.clone();
|
|---|
| 1054 | self.body.body = rest.concat(self.body.body.slice(1));
|
|---|
| 1055 | self.body = self.body.transform(compressor);
|
|---|
| 1056 | } else {
|
|---|
| 1057 | self.body = make_node(AST_BlockStatement, self.body, {
|
|---|
| 1058 | body: rest
|
|---|
| 1059 | }).transform(compressor);
|
|---|
| 1060 | }
|
|---|
| 1061 | self = if_break_in_loop(self, compressor);
|
|---|
| 1062 | }
|
|---|
| 1063 | }
|
|---|
| 1064 |
|
|---|
| 1065 | def_optimize(AST_For, function(self, compressor) {
|
|---|
| 1066 | if (!compressor.option("loops")) return self;
|
|---|
| 1067 | if (compressor.option("side_effects") && self.init) {
|
|---|
| 1068 | self.init = self.init.drop_side_effect_free(compressor);
|
|---|
| 1069 | }
|
|---|
| 1070 | if (self.condition) {
|
|---|
| 1071 | var cond = self.condition.evaluate(compressor);
|
|---|
| 1072 | if (!(cond instanceof AST_Node)) {
|
|---|
| 1073 | if (cond) self.condition = null;
|
|---|
| 1074 | else if (!compressor.option("dead_code")) {
|
|---|
| 1075 | var orig = self.condition;
|
|---|
| 1076 | self.condition = make_node_from_constant(cond, self.condition);
|
|---|
| 1077 | self.condition = best_of_expression(self.condition.transform(compressor), orig);
|
|---|
| 1078 | }
|
|---|
| 1079 | }
|
|---|
| 1080 | if (compressor.option("dead_code")) {
|
|---|
| 1081 | if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
|
|---|
| 1082 | if (!cond) {
|
|---|
| 1083 | var body = [];
|
|---|
| 1084 | extract_from_unreachable_code(compressor, self.body, body);
|
|---|
| 1085 | if (self.init instanceof AST_Statement) {
|
|---|
| 1086 | body.push(self.init);
|
|---|
| 1087 | } else if (self.init) {
|
|---|
| 1088 | body.push(make_node(AST_SimpleStatement, self.init, {
|
|---|
| 1089 | body: self.init
|
|---|
| 1090 | }));
|
|---|
| 1091 | }
|
|---|
| 1092 | body.push(make_node(AST_SimpleStatement, self.condition, {
|
|---|
| 1093 | body: self.condition
|
|---|
| 1094 | }));
|
|---|
| 1095 | return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
|
|---|
| 1096 | }
|
|---|
| 1097 | }
|
|---|
| 1098 | }
|
|---|
| 1099 | return if_break_in_loop(self, compressor);
|
|---|
| 1100 | });
|
|---|
| 1101 |
|
|---|
| 1102 | def_optimize(AST_If, function(self, compressor) {
|
|---|
| 1103 | if (is_empty(self.alternative)) self.alternative = null;
|
|---|
| 1104 |
|
|---|
| 1105 | if (!compressor.option("conditionals")) return self;
|
|---|
| 1106 | // if condition can be statically determined, drop
|
|---|
| 1107 | // one of the blocks. note, statically determined implies
|
|---|
| 1108 | // “has no side effects”; also it doesn't work for cases like
|
|---|
| 1109 | // `x && true`, though it probably should.
|
|---|
| 1110 | var cond = self.condition.evaluate(compressor);
|
|---|
| 1111 | if (!compressor.option("dead_code") && !(cond instanceof AST_Node)) {
|
|---|
| 1112 | var orig = self.condition;
|
|---|
| 1113 | self.condition = make_node_from_constant(cond, orig);
|
|---|
| 1114 | self.condition = best_of_expression(self.condition.transform(compressor), orig);
|
|---|
| 1115 | }
|
|---|
| 1116 | if (compressor.option("dead_code")) {
|
|---|
| 1117 | if (cond instanceof AST_Node) cond = self.condition.tail_node().evaluate(compressor);
|
|---|
| 1118 | if (!cond) {
|
|---|
| 1119 | var body = [];
|
|---|
| 1120 | extract_from_unreachable_code(compressor, self.body, body);
|
|---|
| 1121 | body.push(make_node(AST_SimpleStatement, self.condition, {
|
|---|
| 1122 | body: self.condition
|
|---|
| 1123 | }));
|
|---|
| 1124 | if (self.alternative) body.push(self.alternative);
|
|---|
| 1125 | return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
|
|---|
| 1126 | } else if (!(cond instanceof AST_Node)) {
|
|---|
| 1127 | var body = [];
|
|---|
| 1128 | body.push(make_node(AST_SimpleStatement, self.condition, {
|
|---|
| 1129 | body: self.condition
|
|---|
| 1130 | }));
|
|---|
| 1131 | body.push(self.body);
|
|---|
| 1132 | if (self.alternative) {
|
|---|
| 1133 | extract_from_unreachable_code(compressor, self.alternative, body);
|
|---|
| 1134 | }
|
|---|
| 1135 | return make_node(AST_BlockStatement, self, { body: body }).optimize(compressor);
|
|---|
| 1136 | }
|
|---|
| 1137 | }
|
|---|
| 1138 | var negated = self.condition.negate(compressor);
|
|---|
| 1139 | var self_condition_length = self.condition.size();
|
|---|
| 1140 | var negated_length = negated.size();
|
|---|
| 1141 | var negated_is_best = negated_length < self_condition_length;
|
|---|
| 1142 | if (self.alternative && negated_is_best) {
|
|---|
| 1143 | negated_is_best = false; // because we already do the switch here.
|
|---|
| 1144 | // no need to swap values of self_condition_length and negated_length
|
|---|
| 1145 | // here because they are only used in an equality comparison later on.
|
|---|
| 1146 | self.condition = negated;
|
|---|
| 1147 | var tmp = self.body;
|
|---|
| 1148 | self.body = self.alternative || make_node(AST_EmptyStatement, self);
|
|---|
| 1149 | self.alternative = tmp;
|
|---|
| 1150 | }
|
|---|
| 1151 | if (is_empty(self.body) && is_empty(self.alternative)) {
|
|---|
| 1152 | return make_node(AST_SimpleStatement, self.condition, {
|
|---|
| 1153 | body: self.condition.clone()
|
|---|
| 1154 | }).optimize(compressor);
|
|---|
| 1155 | }
|
|---|
| 1156 | if (self.body instanceof AST_SimpleStatement
|
|---|
| 1157 | && self.alternative instanceof AST_SimpleStatement) {
|
|---|
| 1158 | return make_node(AST_SimpleStatement, self, {
|
|---|
| 1159 | body: make_node(AST_Conditional, self, {
|
|---|
| 1160 | condition : self.condition,
|
|---|
| 1161 | consequent : self.body.body,
|
|---|
| 1162 | alternative : self.alternative.body
|
|---|
| 1163 | })
|
|---|
| 1164 | }).optimize(compressor);
|
|---|
| 1165 | }
|
|---|
| 1166 | if (is_empty(self.alternative) && self.body instanceof AST_SimpleStatement) {
|
|---|
| 1167 | if (self_condition_length === negated_length && !negated_is_best
|
|---|
| 1168 | && self.condition instanceof AST_Binary && self.condition.operator == "||") {
|
|---|
| 1169 | // although the code length of self.condition and negated are the same,
|
|---|
| 1170 | // negated does not require additional surrounding parentheses.
|
|---|
| 1171 | // see https://github.com/mishoo/UglifyJS2/issues/979
|
|---|
| 1172 | negated_is_best = true;
|
|---|
| 1173 | }
|
|---|
| 1174 | if (negated_is_best) return make_node(AST_SimpleStatement, self, {
|
|---|
| 1175 | body: make_node(AST_Binary, self, {
|
|---|
| 1176 | operator : "||",
|
|---|
| 1177 | left : negated,
|
|---|
| 1178 | right : self.body.body
|
|---|
| 1179 | })
|
|---|
| 1180 | }).optimize(compressor);
|
|---|
| 1181 | return make_node(AST_SimpleStatement, self, {
|
|---|
| 1182 | body: make_node(AST_Binary, self, {
|
|---|
| 1183 | operator : "&&",
|
|---|
| 1184 | left : self.condition,
|
|---|
| 1185 | right : self.body.body
|
|---|
| 1186 | })
|
|---|
| 1187 | }).optimize(compressor);
|
|---|
| 1188 | }
|
|---|
| 1189 | if (self.body instanceof AST_EmptyStatement
|
|---|
| 1190 | && self.alternative instanceof AST_SimpleStatement) {
|
|---|
| 1191 | return make_node(AST_SimpleStatement, self, {
|
|---|
| 1192 | body: make_node(AST_Binary, self, {
|
|---|
| 1193 | operator : "||",
|
|---|
| 1194 | left : self.condition,
|
|---|
| 1195 | right : self.alternative.body
|
|---|
| 1196 | })
|
|---|
| 1197 | }).optimize(compressor);
|
|---|
| 1198 | }
|
|---|
| 1199 | if (self.body instanceof AST_Exit
|
|---|
| 1200 | && self.alternative instanceof AST_Exit
|
|---|
| 1201 | && self.body.TYPE == self.alternative.TYPE) {
|
|---|
| 1202 | return make_node(self.body.CTOR, self, {
|
|---|
| 1203 | value: make_node(AST_Conditional, self, {
|
|---|
| 1204 | condition : self.condition,
|
|---|
| 1205 | consequent : self.body.value || make_void_0(self.body),
|
|---|
| 1206 | alternative : self.alternative.value || make_void_0(self.alternative),
|
|---|
| 1207 | }).transform(compressor)
|
|---|
| 1208 | }).optimize(compressor);
|
|---|
| 1209 | }
|
|---|
| 1210 | if (self.body instanceof AST_If
|
|---|
| 1211 | && !self.body.alternative
|
|---|
| 1212 | && !self.alternative) {
|
|---|
| 1213 | self = make_node(AST_If, self, {
|
|---|
| 1214 | condition: make_node(AST_Binary, self.condition, {
|
|---|
| 1215 | operator: "&&",
|
|---|
| 1216 | left: self.condition,
|
|---|
| 1217 | right: self.body.condition
|
|---|
| 1218 | }),
|
|---|
| 1219 | body: self.body.body,
|
|---|
| 1220 | alternative: null
|
|---|
| 1221 | });
|
|---|
| 1222 | }
|
|---|
| 1223 | if (aborts(self.body)) {
|
|---|
| 1224 | if (self.alternative) {
|
|---|
| 1225 | var alt = self.alternative;
|
|---|
| 1226 | self.alternative = null;
|
|---|
| 1227 | return make_node(AST_BlockStatement, self, {
|
|---|
| 1228 | body: [ self, alt ]
|
|---|
| 1229 | }).optimize(compressor);
|
|---|
| 1230 | }
|
|---|
| 1231 | }
|
|---|
| 1232 | if (aborts(self.alternative)) {
|
|---|
| 1233 | var body = self.body;
|
|---|
| 1234 | self.body = self.alternative;
|
|---|
| 1235 | self.condition = negated_is_best ? negated : self.condition.negate(compressor);
|
|---|
| 1236 | self.alternative = null;
|
|---|
| 1237 | return make_node(AST_BlockStatement, self, {
|
|---|
| 1238 | body: [ self, body ]
|
|---|
| 1239 | }).optimize(compressor);
|
|---|
| 1240 | }
|
|---|
| 1241 | return self;
|
|---|
| 1242 | });
|
|---|
| 1243 |
|
|---|
| 1244 | def_optimize(AST_Switch, function(self, compressor) {
|
|---|
| 1245 | if (!compressor.option("switches")) return self;
|
|---|
| 1246 | var branch;
|
|---|
| 1247 | var value = self.expression.evaluate(compressor);
|
|---|
| 1248 | if (!(value instanceof AST_Node)) {
|
|---|
| 1249 | var orig = self.expression;
|
|---|
| 1250 | self.expression = make_node_from_constant(value, orig);
|
|---|
| 1251 | self.expression = best_of_expression(self.expression.transform(compressor), orig);
|
|---|
| 1252 | }
|
|---|
| 1253 | if (!compressor.option("dead_code")) return self;
|
|---|
| 1254 | if (value instanceof AST_Node) {
|
|---|
| 1255 | value = self.expression.tail_node().evaluate(compressor);
|
|---|
| 1256 | }
|
|---|
| 1257 | var decl = [];
|
|---|
| 1258 | var body = [];
|
|---|
| 1259 | var default_branch;
|
|---|
| 1260 | var exact_match;
|
|---|
| 1261 | // - compress self.body into `body`
|
|---|
| 1262 | // - find and deduplicate default branch
|
|---|
| 1263 | // - find the exact match (`case 1234` inside `switch(1234)`)
|
|---|
| 1264 | for (var i = 0, len = self.body.length; i < len && !exact_match; i++) {
|
|---|
| 1265 | branch = self.body[i];
|
|---|
| 1266 | if (branch instanceof AST_Default) {
|
|---|
| 1267 | if (!default_branch) {
|
|---|
| 1268 | default_branch = branch;
|
|---|
| 1269 | } else {
|
|---|
| 1270 | eliminate_branch(branch, body[body.length - 1]);
|
|---|
| 1271 | }
|
|---|
| 1272 | } else if (!(value instanceof AST_Node)) {
|
|---|
| 1273 | var exp = branch.expression.evaluate(compressor);
|
|---|
| 1274 | if (!(exp instanceof AST_Node) && exp !== value) {
|
|---|
| 1275 | eliminate_branch(branch, body[body.length - 1]);
|
|---|
| 1276 | continue;
|
|---|
| 1277 | }
|
|---|
| 1278 | if (exp instanceof AST_Node && !exp.has_side_effects(compressor)) {
|
|---|
| 1279 | exp = branch.expression.tail_node().evaluate(compressor);
|
|---|
| 1280 | }
|
|---|
| 1281 | if (exp === value) {
|
|---|
| 1282 | exact_match = branch;
|
|---|
| 1283 | if (default_branch) {
|
|---|
| 1284 | var default_index = body.indexOf(default_branch);
|
|---|
| 1285 | body.splice(default_index, 1);
|
|---|
| 1286 | eliminate_branch(default_branch, body[default_index - 1]);
|
|---|
| 1287 | default_branch = null;
|
|---|
| 1288 | }
|
|---|
| 1289 | }
|
|---|
| 1290 | }
|
|---|
| 1291 | body.push(branch);
|
|---|
| 1292 | }
|
|---|
| 1293 | // i < len if we found an exact_match. eliminate the rest
|
|---|
| 1294 | while (i < len) eliminate_branch(self.body[i++], body[body.length - 1]);
|
|---|
| 1295 | self.body = body;
|
|---|
| 1296 |
|
|---|
| 1297 | let default_or_exact = default_branch || exact_match;
|
|---|
| 1298 | default_branch = null;
|
|---|
| 1299 | exact_match = null;
|
|---|
| 1300 |
|
|---|
| 1301 | // group equivalent branches so they will be located next to each other,
|
|---|
| 1302 | // that way the next micro-optimization will merge them.
|
|---|
| 1303 | // ** bail micro-optimization if not a simple switch case with breaks
|
|---|
| 1304 | if (body.every((branch, i) =>
|
|---|
| 1305 | (branch === default_or_exact || branch.expression instanceof AST_Constant)
|
|---|
| 1306 | && (branch.body.length === 0 || aborts(branch) || body.length - 1 === i))
|
|---|
| 1307 | ) {
|
|---|
| 1308 | for (let i = 0; i < body.length; i++) {
|
|---|
| 1309 | const branch = body[i];
|
|---|
| 1310 | for (let j = i + 1; j < body.length; j++) {
|
|---|
| 1311 | const next = body[j];
|
|---|
| 1312 | if (next.body.length === 0) continue;
|
|---|
| 1313 | const last_branch = j === (body.length - 1);
|
|---|
| 1314 | const equivalentBranch = branches_equivalent(next, branch, false);
|
|---|
| 1315 | if (equivalentBranch || (last_branch && branches_equivalent(next, branch, true))) {
|
|---|
| 1316 | if (!equivalentBranch && last_branch) {
|
|---|
| 1317 | next.body.push(make_node(AST_Break));
|
|---|
| 1318 | }
|
|---|
| 1319 |
|
|---|
| 1320 | // let's find previous siblings with inert fallthrough...
|
|---|
| 1321 | let x = j - 1;
|
|---|
| 1322 | let fallthroughDepth = 0;
|
|---|
| 1323 | while (x > i) {
|
|---|
| 1324 | if (is_inert_body(body[x--])) {
|
|---|
| 1325 | fallthroughDepth++;
|
|---|
| 1326 | } else {
|
|---|
| 1327 | break;
|
|---|
| 1328 | }
|
|---|
| 1329 | }
|
|---|
| 1330 |
|
|---|
| 1331 | const plucked = body.splice(j - fallthroughDepth, 1 + fallthroughDepth);
|
|---|
| 1332 | body.splice(i + 1, 0, ...plucked);
|
|---|
| 1333 | i += plucked.length;
|
|---|
| 1334 | }
|
|---|
| 1335 | }
|
|---|
| 1336 | }
|
|---|
| 1337 | }
|
|---|
| 1338 |
|
|---|
| 1339 | // merge equivalent branches in a row
|
|---|
| 1340 | for (let i = 0; i < body.length; i++) {
|
|---|
| 1341 | let branch = body[i];
|
|---|
| 1342 | if (branch.body.length === 0) continue;
|
|---|
| 1343 | if (!aborts(branch)) continue;
|
|---|
| 1344 |
|
|---|
| 1345 | for (let j = i + 1; j < body.length; i++, j++) {
|
|---|
| 1346 | let next = body[j];
|
|---|
| 1347 | if (next.body.length === 0) continue;
|
|---|
| 1348 | if (
|
|---|
| 1349 | branches_equivalent(next, branch, false)
|
|---|
| 1350 | || (j === body.length - 1 && branches_equivalent(next, branch, true))
|
|---|
| 1351 | ) {
|
|---|
| 1352 | branch.body = [];
|
|---|
| 1353 | branch = next;
|
|---|
| 1354 | continue;
|
|---|
| 1355 | }
|
|---|
| 1356 | break;
|
|---|
| 1357 | }
|
|---|
| 1358 | }
|
|---|
| 1359 |
|
|---|
| 1360 | // Prune any empty branches at the end of the switch statement.
|
|---|
| 1361 | {
|
|---|
| 1362 | let i = body.length - 1;
|
|---|
| 1363 | for (; i >= 0; i--) {
|
|---|
| 1364 | let bbody = body[i].body;
|
|---|
| 1365 | while (is_break(bbody[bbody.length - 1], compressor)) bbody.pop();
|
|---|
| 1366 | if (!is_inert_body(body[i])) break;
|
|---|
| 1367 | }
|
|---|
| 1368 | // i now points to the index of a branch that contains a body. By incrementing, it's
|
|---|
| 1369 | // pointing to the first branch that's empty.
|
|---|
| 1370 | i++;
|
|---|
| 1371 | if (!default_or_exact || body.indexOf(default_or_exact) >= i) {
|
|---|
| 1372 | // The default behavior is to do nothing. We can take advantage of that to
|
|---|
| 1373 | // remove all case expressions that are side-effect free that also do
|
|---|
| 1374 | // nothing, since they'll default to doing nothing. But we can't remove any
|
|---|
| 1375 | // case expressions before one that would side-effect, since they may cause
|
|---|
| 1376 | // the side-effect to be skipped.
|
|---|
| 1377 | for (let j = body.length - 1; j >= i; j--) {
|
|---|
| 1378 | let branch = body[j];
|
|---|
| 1379 | if (branch === default_or_exact) {
|
|---|
| 1380 | default_or_exact = null;
|
|---|
| 1381 | eliminate_branch(body.pop());
|
|---|
| 1382 | } else if (!branch.expression.has_side_effects(compressor)) {
|
|---|
| 1383 | eliminate_branch(body.pop());
|
|---|
| 1384 | } else {
|
|---|
| 1385 | break;
|
|---|
| 1386 | }
|
|---|
| 1387 | }
|
|---|
| 1388 | }
|
|---|
| 1389 | }
|
|---|
| 1390 |
|
|---|
| 1391 |
|
|---|
| 1392 | // Prune side-effect free branches that fall into default.
|
|---|
| 1393 | DEFAULT: if (default_or_exact) {
|
|---|
| 1394 | let default_index = body.indexOf(default_or_exact);
|
|---|
| 1395 | let default_body_index = default_index;
|
|---|
| 1396 | for (; default_body_index < body.length - 1; default_body_index++) {
|
|---|
| 1397 | if (!is_inert_body(body[default_body_index])) break;
|
|---|
| 1398 | }
|
|---|
| 1399 | if (default_body_index < body.length - 1) {
|
|---|
| 1400 | break DEFAULT;
|
|---|
| 1401 | }
|
|---|
| 1402 |
|
|---|
| 1403 | let side_effect_index = body.length - 1;
|
|---|
| 1404 | for (; side_effect_index >= 0; side_effect_index--) {
|
|---|
| 1405 | let branch = body[side_effect_index];
|
|---|
| 1406 | if (branch === default_or_exact) continue;
|
|---|
| 1407 | if (branch.expression.has_side_effects(compressor)) break;
|
|---|
| 1408 | }
|
|---|
| 1409 | // If the default behavior comes after any side-effect case expressions,
|
|---|
| 1410 | // then we can fold all side-effect free cases into the default branch.
|
|---|
| 1411 | // If the side-effect case is after the default, then any side-effect
|
|---|
| 1412 | // free cases could prevent the side-effect from occurring.
|
|---|
| 1413 | if (default_body_index > side_effect_index) {
|
|---|
| 1414 | let prev_body_index = default_index - 1;
|
|---|
| 1415 | for (; prev_body_index >= 0; prev_body_index--) {
|
|---|
| 1416 | if (!is_inert_body(body[prev_body_index])) break;
|
|---|
| 1417 | }
|
|---|
| 1418 | let before = Math.max(side_effect_index, prev_body_index) + 1;
|
|---|
| 1419 | let after = default_index;
|
|---|
| 1420 | if (side_effect_index > default_index) {
|
|---|
| 1421 | // If the default falls into the same body as a side-effect
|
|---|
| 1422 | // case, then we need preserve that case and only prune the
|
|---|
| 1423 | // cases after it.
|
|---|
| 1424 | after = side_effect_index;
|
|---|
| 1425 | body[side_effect_index].body = body[default_body_index].body;
|
|---|
| 1426 | } else {
|
|---|
| 1427 | // The default will be the last branch.
|
|---|
| 1428 | default_or_exact.body = body[default_body_index].body;
|
|---|
| 1429 | }
|
|---|
| 1430 |
|
|---|
| 1431 | // Prune everything after the default (or last side-effect case)
|
|---|
| 1432 | // until the next case with a body.
|
|---|
| 1433 | body.splice(after + 1, default_body_index - after);
|
|---|
| 1434 | // Prune everything before the default that falls into it.
|
|---|
| 1435 | body.splice(before, default_index - before);
|
|---|
| 1436 | }
|
|---|
| 1437 | }
|
|---|
| 1438 |
|
|---|
| 1439 | // See if we can remove the switch entirely if all cases (the default) fall into the same case body.
|
|---|
| 1440 | DEFAULT: if (default_or_exact) {
|
|---|
| 1441 | let i = body.findIndex(branch => !is_inert_body(branch));
|
|---|
| 1442 | let caseBody;
|
|---|
| 1443 | // `i` is equal to one of the following:
|
|---|
| 1444 | // - `-1`, there is no body in the switch statement.
|
|---|
| 1445 | // - `body.length - 1`, all cases fall into the same body.
|
|---|
| 1446 | // - anything else, there are multiple bodies in the switch.
|
|---|
| 1447 | if (i === body.length - 1) {
|
|---|
| 1448 | // All cases fall into the case body.
|
|---|
| 1449 | let branch = body[i];
|
|---|
| 1450 | if (has_nested_break(self)) break DEFAULT;
|
|---|
| 1451 |
|
|---|
| 1452 | // This is the last case body, and we've already pruned any breaks, so it's
|
|---|
| 1453 | // safe to hoist.
|
|---|
| 1454 | caseBody = make_node(AST_BlockStatement, branch, {
|
|---|
| 1455 | body: branch.body
|
|---|
| 1456 | });
|
|---|
| 1457 | branch.body = [];
|
|---|
| 1458 | } else if (i !== -1) {
|
|---|
| 1459 | // If there are multiple bodies, then we cannot optimize anything.
|
|---|
| 1460 | break DEFAULT;
|
|---|
| 1461 | }
|
|---|
| 1462 |
|
|---|
| 1463 | let sideEffect = body.find(
|
|---|
| 1464 | branch => branch !== default_or_exact && branch.expression.has_side_effects(compressor)
|
|---|
| 1465 | );
|
|---|
| 1466 | // If no cases cause a side-effect, we can eliminate the switch entirely.
|
|---|
| 1467 | if (!sideEffect) {
|
|---|
| 1468 | return make_node(AST_BlockStatement, self, {
|
|---|
| 1469 | body: decl.concat(
|
|---|
| 1470 | statement(self.expression),
|
|---|
| 1471 | default_or_exact.expression ? statement(default_or_exact.expression) : [],
|
|---|
| 1472 | caseBody || []
|
|---|
| 1473 | )
|
|---|
| 1474 | }).optimize(compressor);
|
|---|
| 1475 | }
|
|---|
| 1476 |
|
|---|
| 1477 | // If we're this far, either there was no body or all cases fell into the same body.
|
|---|
| 1478 | // If there was no body, then we don't need a default branch (because the default is
|
|---|
| 1479 | // do nothing). If there was a body, we'll extract it to after the switch, so the
|
|---|
| 1480 | // switch's new default is to do nothing and we can still prune it.
|
|---|
| 1481 | const default_index = body.indexOf(default_or_exact);
|
|---|
| 1482 | body.splice(default_index, 1);
|
|---|
| 1483 | default_or_exact = null;
|
|---|
| 1484 |
|
|---|
| 1485 | if (caseBody) {
|
|---|
| 1486 | // Recurse into switch statement one more time so that we can append the case body
|
|---|
| 1487 | // outside of the switch. This recursion will only happen once since we've pruned
|
|---|
| 1488 | // the default case.
|
|---|
| 1489 | return make_node(AST_BlockStatement, self, {
|
|---|
| 1490 | body: decl.concat(self, caseBody)
|
|---|
| 1491 | }).optimize(compressor);
|
|---|
| 1492 | }
|
|---|
| 1493 | // If we fall here, there is a default branch somewhere, there are no case bodies,
|
|---|
| 1494 | // and there's a side-effect somewhere. Just let the below paths take care of it.
|
|---|
| 1495 | }
|
|---|
| 1496 |
|
|---|
| 1497 | // Reintegrate `decl` (var statements)
|
|---|
| 1498 | if (body.length > 0) {
|
|---|
| 1499 | body[0].body = decl.concat(body[0].body);
|
|---|
| 1500 | }
|
|---|
| 1501 | if (body.length == 0) {
|
|---|
| 1502 | return make_node(AST_BlockStatement, self, {
|
|---|
| 1503 | body: decl.concat(statement(self.expression))
|
|---|
| 1504 | }).optimize(compressor);
|
|---|
| 1505 | }
|
|---|
| 1506 |
|
|---|
| 1507 | if (body.length == 1 && !has_nested_break(self)) {
|
|---|
| 1508 | // This is the last case body, and we've already pruned any breaks, so it's
|
|---|
| 1509 | // safe to hoist.
|
|---|
| 1510 | let branch = body[0];
|
|---|
| 1511 | return make_node(AST_If, self, {
|
|---|
| 1512 | condition: make_node(AST_Binary, self, {
|
|---|
| 1513 | operator: "===",
|
|---|
| 1514 | left: self.expression,
|
|---|
| 1515 | right: branch.expression,
|
|---|
| 1516 | }),
|
|---|
| 1517 | body: make_node(AST_BlockStatement, branch, {
|
|---|
| 1518 | body: branch.body
|
|---|
| 1519 | }),
|
|---|
| 1520 | alternative: null
|
|---|
| 1521 | }).optimize(compressor);
|
|---|
| 1522 | }
|
|---|
| 1523 | if (body.length === 2 && default_or_exact && !has_nested_break(self)) {
|
|---|
| 1524 | let branch = body[0] === default_or_exact ? body[1] : body[0];
|
|---|
| 1525 | let exact_exp = default_or_exact.expression && statement(default_or_exact.expression);
|
|---|
| 1526 | if (aborts(body[0])) {
|
|---|
| 1527 | // Only the first branch body could have a break (at the last statement)
|
|---|
| 1528 | let first = body[0];
|
|---|
| 1529 | if (is_break(first.body[first.body.length - 1], compressor)) {
|
|---|
| 1530 | first.body.pop();
|
|---|
| 1531 | }
|
|---|
| 1532 | return make_node(AST_If, self, {
|
|---|
| 1533 | condition: make_node(AST_Binary, self, {
|
|---|
| 1534 | operator: "===",
|
|---|
| 1535 | left: self.expression,
|
|---|
| 1536 | right: branch.expression,
|
|---|
| 1537 | }),
|
|---|
| 1538 | body: make_node(AST_BlockStatement, branch, {
|
|---|
| 1539 | body: branch.body
|
|---|
| 1540 | }),
|
|---|
| 1541 | alternative: make_node(AST_BlockStatement, default_or_exact, {
|
|---|
| 1542 | body: [].concat(
|
|---|
| 1543 | exact_exp || [],
|
|---|
| 1544 | default_or_exact.body
|
|---|
| 1545 | )
|
|---|
| 1546 | })
|
|---|
| 1547 | }).optimize(compressor);
|
|---|
| 1548 | }
|
|---|
| 1549 | let operator = "===";
|
|---|
| 1550 | let consequent = make_node(AST_BlockStatement, branch, {
|
|---|
| 1551 | body: branch.body,
|
|---|
| 1552 | });
|
|---|
| 1553 | let always = make_node(AST_BlockStatement, default_or_exact, {
|
|---|
| 1554 | body: [].concat(
|
|---|
| 1555 | exact_exp || [],
|
|---|
| 1556 | default_or_exact.body
|
|---|
| 1557 | )
|
|---|
| 1558 | });
|
|---|
| 1559 | if (body[0] === default_or_exact) {
|
|---|
| 1560 | operator = "!==";
|
|---|
| 1561 | let tmp = always;
|
|---|
| 1562 | always = consequent;
|
|---|
| 1563 | consequent = tmp;
|
|---|
| 1564 | }
|
|---|
| 1565 | return make_node(AST_BlockStatement, self, {
|
|---|
| 1566 | body: [
|
|---|
| 1567 | make_node(AST_If, self, {
|
|---|
| 1568 | condition: make_node(AST_Binary, self, {
|
|---|
| 1569 | operator: operator,
|
|---|
| 1570 | left: self.expression,
|
|---|
| 1571 | right: branch.expression,
|
|---|
| 1572 | }),
|
|---|
| 1573 | body: consequent,
|
|---|
| 1574 | alternative: null,
|
|---|
| 1575 | }),
|
|---|
| 1576 | always,
|
|---|
| 1577 | ],
|
|---|
| 1578 | }).optimize(compressor);
|
|---|
| 1579 | }
|
|---|
| 1580 | return self;
|
|---|
| 1581 |
|
|---|
| 1582 | function eliminate_branch(branch, prev) {
|
|---|
| 1583 | if (prev && !aborts(prev)) {
|
|---|
| 1584 | prev.body = prev.body.concat(branch.body);
|
|---|
| 1585 | } else {
|
|---|
| 1586 | extract_from_unreachable_code(compressor, branch, decl);
|
|---|
| 1587 | }
|
|---|
| 1588 | }
|
|---|
| 1589 | function branches_equivalent(branch, prev, insertBreak) {
|
|---|
| 1590 | let bbody = branch.body;
|
|---|
| 1591 | let pbody = prev.body;
|
|---|
| 1592 | if (insertBreak) {
|
|---|
| 1593 | bbody = bbody.concat(make_node(AST_Break));
|
|---|
| 1594 | }
|
|---|
| 1595 | if (bbody.length !== pbody.length) return false;
|
|---|
| 1596 | let bblock = make_node(AST_BlockStatement, branch, { body: bbody });
|
|---|
| 1597 | let pblock = make_node(AST_BlockStatement, prev, { body: pbody });
|
|---|
| 1598 | return bblock.equivalent_to(pblock);
|
|---|
| 1599 | }
|
|---|
| 1600 | function statement(body) {
|
|---|
| 1601 | return make_node(AST_SimpleStatement, body, { body });
|
|---|
| 1602 | }
|
|---|
| 1603 | function has_nested_break(root) {
|
|---|
| 1604 | let has_break = false;
|
|---|
| 1605 |
|
|---|
| 1606 | let tw = new TreeWalker(node => {
|
|---|
| 1607 | if (has_break) return true;
|
|---|
| 1608 | if (node instanceof AST_Lambda) return true;
|
|---|
| 1609 | if (node instanceof AST_SimpleStatement) return true;
|
|---|
| 1610 | if (!is_break(node, tw)) return;
|
|---|
| 1611 | let parent = tw.parent();
|
|---|
| 1612 | if (
|
|---|
| 1613 | parent instanceof AST_SwitchBranch
|
|---|
| 1614 | && parent.body[parent.body.length - 1] === node
|
|---|
| 1615 | ) {
|
|---|
| 1616 | return;
|
|---|
| 1617 | }
|
|---|
| 1618 | has_break = true;
|
|---|
| 1619 | });
|
|---|
| 1620 | root.walk(tw);
|
|---|
| 1621 | return has_break;
|
|---|
| 1622 | }
|
|---|
| 1623 | function is_break(node, stack) {
|
|---|
| 1624 | return node instanceof AST_Break
|
|---|
| 1625 | && stack.loopcontrol_target(node) === self;
|
|---|
| 1626 | }
|
|---|
| 1627 | function is_inert_body(branch) {
|
|---|
| 1628 | return !aborts(branch) && !make_node(AST_BlockStatement, branch, {
|
|---|
| 1629 | body: branch.body
|
|---|
| 1630 | }).has_side_effects(compressor);
|
|---|
| 1631 | }
|
|---|
| 1632 | });
|
|---|
| 1633 |
|
|---|
| 1634 | def_optimize(AST_Try, function(self, compressor) {
|
|---|
| 1635 | if (self.bcatch && self.bfinally && self.bfinally.body.every(is_empty)) self.bfinally = null;
|
|---|
| 1636 |
|
|---|
| 1637 | if (compressor.option("dead_code") && self.body.body.every(is_empty)) {
|
|---|
| 1638 | var body = [];
|
|---|
| 1639 | if (self.bcatch) {
|
|---|
| 1640 | extract_from_unreachable_code(compressor, self.bcatch, body);
|
|---|
| 1641 | }
|
|---|
| 1642 | if (self.bfinally) body.push(...self.bfinally.body);
|
|---|
| 1643 | return make_node(AST_BlockStatement, self, {
|
|---|
| 1644 | body: body
|
|---|
| 1645 | }).optimize(compressor);
|
|---|
| 1646 | }
|
|---|
| 1647 | return self;
|
|---|
| 1648 | });
|
|---|
| 1649 |
|
|---|
| 1650 | AST_Definitions.DEFMETHOD("to_assignments", function(compressor) {
|
|---|
| 1651 | var reduce_vars = compressor.option("reduce_vars");
|
|---|
| 1652 | var assignments = [];
|
|---|
| 1653 |
|
|---|
| 1654 | for (const def of this.definitions) {
|
|---|
| 1655 | if (def.value) {
|
|---|
| 1656 | var name = make_node(AST_SymbolRef, def.name, def.name);
|
|---|
| 1657 | assignments.push(make_node(AST_Assign, def, {
|
|---|
| 1658 | operator : "=",
|
|---|
| 1659 | logical: false,
|
|---|
| 1660 | left : name,
|
|---|
| 1661 | right : def.value
|
|---|
| 1662 | }));
|
|---|
| 1663 | if (reduce_vars) name.definition().fixed = false;
|
|---|
| 1664 | }
|
|---|
| 1665 | const thedef = def.name.definition();
|
|---|
| 1666 | thedef.eliminated++;
|
|---|
| 1667 | thedef.replaced--;
|
|---|
| 1668 | }
|
|---|
| 1669 |
|
|---|
| 1670 | if (assignments.length == 0) return null;
|
|---|
| 1671 | return make_sequence(this, assignments);
|
|---|
| 1672 | });
|
|---|
| 1673 |
|
|---|
| 1674 | def_optimize(AST_Definitions, function(self) {
|
|---|
| 1675 | if (self.definitions.length == 0) {
|
|---|
| 1676 | return make_node(AST_EmptyStatement, self);
|
|---|
| 1677 | }
|
|---|
| 1678 | return self;
|
|---|
| 1679 | });
|
|---|
| 1680 |
|
|---|
| 1681 | def_optimize(AST_VarDef, function(self, compressor) {
|
|---|
| 1682 | if (
|
|---|
| 1683 | self.name instanceof AST_SymbolLet
|
|---|
| 1684 | && self.value != null
|
|---|
| 1685 | && is_undefined(self.value, compressor)
|
|---|
| 1686 | ) {
|
|---|
| 1687 | self.value = null;
|
|---|
| 1688 | }
|
|---|
| 1689 | return self;
|
|---|
| 1690 | });
|
|---|
| 1691 |
|
|---|
| 1692 | def_optimize(AST_Import, function(self) {
|
|---|
| 1693 | return self;
|
|---|
| 1694 | });
|
|---|
| 1695 |
|
|---|
| 1696 | def_optimize(AST_Call, function(self, compressor) {
|
|---|
| 1697 | var exp = self.expression;
|
|---|
| 1698 | var fn = exp;
|
|---|
| 1699 | inline_array_like_spread(self.args);
|
|---|
| 1700 | var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion));
|
|---|
| 1701 |
|
|---|
| 1702 | if (compressor.option("reduce_vars") && fn instanceof AST_SymbolRef) {
|
|---|
| 1703 | fn = fn.fixed_value();
|
|---|
| 1704 | }
|
|---|
| 1705 |
|
|---|
| 1706 | var is_func = fn instanceof AST_Lambda;
|
|---|
| 1707 |
|
|---|
| 1708 | if (is_func && fn.pinned()) return self;
|
|---|
| 1709 |
|
|---|
| 1710 | if (compressor.option("unused")
|
|---|
| 1711 | && simple_args
|
|---|
| 1712 | && is_func
|
|---|
| 1713 | && !fn.uses_arguments) {
|
|---|
| 1714 | var pos = 0, last = 0;
|
|---|
| 1715 | for (var i = 0, len = self.args.length; i < len; i++) {
|
|---|
| 1716 | if (fn.argnames[i] instanceof AST_Expansion) {
|
|---|
| 1717 | if (has_flag(fn.argnames[i].expression, UNUSED)) while (i < len) {
|
|---|
| 1718 | var node = self.args[i++].drop_side_effect_free(compressor);
|
|---|
| 1719 | if (node) {
|
|---|
| 1720 | self.args[pos++] = node;
|
|---|
| 1721 | }
|
|---|
| 1722 | } else while (i < len) {
|
|---|
| 1723 | self.args[pos++] = self.args[i++];
|
|---|
| 1724 | }
|
|---|
| 1725 | last = pos;
|
|---|
| 1726 | break;
|
|---|
| 1727 | }
|
|---|
| 1728 | var trim = i >= fn.argnames.length;
|
|---|
| 1729 | if (trim || has_flag(fn.argnames[i], UNUSED)) {
|
|---|
| 1730 | var node = self.args[i].drop_side_effect_free(compressor);
|
|---|
| 1731 | if (node) {
|
|---|
| 1732 | self.args[pos++] = node;
|
|---|
| 1733 | } else if (!trim) {
|
|---|
| 1734 | self.args[pos++] = make_node(AST_Number, self.args[i], {
|
|---|
| 1735 | value: 0
|
|---|
| 1736 | });
|
|---|
| 1737 | continue;
|
|---|
| 1738 | }
|
|---|
| 1739 | } else {
|
|---|
| 1740 | self.args[pos++] = self.args[i];
|
|---|
| 1741 | }
|
|---|
| 1742 | last = pos;
|
|---|
| 1743 | }
|
|---|
| 1744 | self.args.length = last;
|
|---|
| 1745 | }
|
|---|
| 1746 |
|
|---|
| 1747 | if (
|
|---|
| 1748 | exp instanceof AST_Dot
|
|---|
| 1749 | && exp.expression instanceof AST_SymbolRef
|
|---|
| 1750 | && exp.expression.name === "console"
|
|---|
| 1751 | && exp.expression.definition().undeclared
|
|---|
| 1752 | && exp.property === "assert"
|
|---|
| 1753 | ) {
|
|---|
| 1754 | const condition = self.args[0];
|
|---|
| 1755 | if (condition) {
|
|---|
| 1756 | const value = condition.evaluate(compressor);
|
|---|
| 1757 |
|
|---|
| 1758 | if (value === 1 || value === true) {
|
|---|
| 1759 | return make_void_0(self).optimize(compressor);
|
|---|
| 1760 | }
|
|---|
| 1761 | }
|
|---|
| 1762 | }
|
|---|
| 1763 |
|
|---|
| 1764 | if (compressor.option("unsafe") && !exp.contains_optional()) {
|
|---|
| 1765 | if (exp instanceof AST_Dot && exp.start.value === "Array" && exp.property === "from" && self.args.length === 1) {
|
|---|
| 1766 | const [argument] = self.args;
|
|---|
| 1767 | if (argument instanceof AST_Array) {
|
|---|
| 1768 | return make_node(AST_Array, argument, {
|
|---|
| 1769 | elements: argument.elements
|
|---|
| 1770 | }).optimize(compressor);
|
|---|
| 1771 | }
|
|---|
| 1772 | }
|
|---|
| 1773 | if (is_undeclared_ref(exp)) switch (exp.name) {
|
|---|
| 1774 | case "Array":
|
|---|
| 1775 | if (self.args.length != 1) {
|
|---|
| 1776 | return make_node(AST_Array, self, {
|
|---|
| 1777 | elements: self.args
|
|---|
| 1778 | }).optimize(compressor);
|
|---|
| 1779 | } else if (self.args[0] instanceof AST_Number && self.args[0].value <= 11) {
|
|---|
| 1780 | const elements = [];
|
|---|
| 1781 | for (let i = 0; i < self.args[0].value; i++) elements.push(new AST_Hole);
|
|---|
| 1782 | return new AST_Array({ elements });
|
|---|
| 1783 | }
|
|---|
| 1784 | break;
|
|---|
| 1785 | case "Object":
|
|---|
| 1786 | if (self.args.length == 0) {
|
|---|
| 1787 | return make_node(AST_Object, self, {
|
|---|
| 1788 | properties: []
|
|---|
| 1789 | });
|
|---|
| 1790 | }
|
|---|
| 1791 | break;
|
|---|
| 1792 | case "String":
|
|---|
| 1793 | if (self.args.length == 0) return make_node(AST_String, self, {
|
|---|
| 1794 | value: ""
|
|---|
| 1795 | });
|
|---|
| 1796 | if (self.args.length <= 1) return make_node(AST_Binary, self, {
|
|---|
| 1797 | left: self.args[0],
|
|---|
| 1798 | operator: "+",
|
|---|
| 1799 | right: make_node(AST_String, self, { value: "" })
|
|---|
| 1800 | }).optimize(compressor);
|
|---|
| 1801 | break;
|
|---|
| 1802 | case "Number":
|
|---|
| 1803 | if (self.args.length == 0) return make_node(AST_Number, self, {
|
|---|
| 1804 | value: 0
|
|---|
| 1805 | });
|
|---|
| 1806 | if (self.args.length == 1 && compressor.option("unsafe_math")) {
|
|---|
| 1807 | return make_node(AST_UnaryPrefix, self, {
|
|---|
| 1808 | expression: self.args[0],
|
|---|
| 1809 | operator: "+"
|
|---|
| 1810 | }).optimize(compressor);
|
|---|
| 1811 | }
|
|---|
| 1812 | break;
|
|---|
| 1813 | case "Symbol":
|
|---|
| 1814 | if (self.args.length == 1 && self.args[0] instanceof AST_String && compressor.option("unsafe_symbols"))
|
|---|
| 1815 | self.args.length = 0;
|
|---|
| 1816 | break;
|
|---|
| 1817 | case "Boolean":
|
|---|
| 1818 | if (self.args.length == 0) return make_node(AST_False, self);
|
|---|
| 1819 | if (self.args.length == 1) return make_node(AST_UnaryPrefix, self, {
|
|---|
| 1820 | expression: make_node(AST_UnaryPrefix, self, {
|
|---|
| 1821 | expression: self.args[0],
|
|---|
| 1822 | operator: "!"
|
|---|
| 1823 | }),
|
|---|
| 1824 | operator: "!"
|
|---|
| 1825 | }).optimize(compressor);
|
|---|
| 1826 | break;
|
|---|
| 1827 | case "RegExp":
|
|---|
| 1828 | var params = [];
|
|---|
| 1829 | if (self.args.length >= 1
|
|---|
| 1830 | && self.args.length <= 2
|
|---|
| 1831 | && self.args.every((arg) => {
|
|---|
| 1832 | var value = arg.evaluate(compressor);
|
|---|
| 1833 | params.push(value);
|
|---|
| 1834 | return arg !== value;
|
|---|
| 1835 | })
|
|---|
| 1836 | && regexp_is_safe(params[0])
|
|---|
| 1837 | ) {
|
|---|
| 1838 | let [ source, flags ] = params;
|
|---|
| 1839 | source = regexp_source_fix(new RegExp(source).source);
|
|---|
| 1840 | const rx = make_node(AST_RegExp, self, {
|
|---|
| 1841 | value: { source, flags }
|
|---|
| 1842 | });
|
|---|
| 1843 | if (rx._eval(compressor) !== rx) {
|
|---|
| 1844 | return rx;
|
|---|
| 1845 | }
|
|---|
| 1846 | }
|
|---|
| 1847 | break;
|
|---|
| 1848 | } else if (exp instanceof AST_Dot) switch(exp.property) {
|
|---|
| 1849 | case "toString":
|
|---|
| 1850 | if (self.args.length == 0 && !exp.expression.may_throw_on_access(compressor)) {
|
|---|
| 1851 | return make_node(AST_Binary, self, {
|
|---|
| 1852 | left: make_node(AST_String, self, { value: "" }),
|
|---|
| 1853 | operator: "+",
|
|---|
| 1854 | right: exp.expression
|
|---|
| 1855 | }).optimize(compressor);
|
|---|
| 1856 | }
|
|---|
| 1857 | break;
|
|---|
| 1858 | case "join":
|
|---|
| 1859 | if (exp.expression instanceof AST_Array) EXIT: {
|
|---|
| 1860 | var separator;
|
|---|
| 1861 | if (self.args.length > 0) {
|
|---|
| 1862 | separator = self.args[0].evaluate(compressor);
|
|---|
| 1863 | if (separator === self.args[0]) break EXIT; // not a constant
|
|---|
| 1864 | }
|
|---|
| 1865 | var elements = [];
|
|---|
| 1866 | var consts = [];
|
|---|
| 1867 | for (var i = 0, len = exp.expression.elements.length; i < len; i++) {
|
|---|
| 1868 | var el = exp.expression.elements[i];
|
|---|
| 1869 | if (el instanceof AST_Expansion) break EXIT;
|
|---|
| 1870 | var value = el.evaluate(compressor);
|
|---|
| 1871 | if (value !== el) {
|
|---|
| 1872 | consts.push(value);
|
|---|
| 1873 | } else {
|
|---|
| 1874 | if (consts.length > 0) {
|
|---|
| 1875 | elements.push(make_node(AST_String, self, {
|
|---|
| 1876 | value: consts.join(separator)
|
|---|
| 1877 | }));
|
|---|
| 1878 | consts.length = 0;
|
|---|
| 1879 | }
|
|---|
| 1880 | elements.push(el);
|
|---|
| 1881 | }
|
|---|
| 1882 | }
|
|---|
| 1883 | if (consts.length > 0) {
|
|---|
| 1884 | elements.push(make_node(AST_String, self, {
|
|---|
| 1885 | value: consts.join(separator)
|
|---|
| 1886 | }));
|
|---|
| 1887 | }
|
|---|
| 1888 | if (elements.length == 0) return make_node(AST_String, self, { value: "" });
|
|---|
| 1889 | if (elements.length == 1) {
|
|---|
| 1890 | if (elements[0].is_string(compressor)) {
|
|---|
| 1891 | return elements[0];
|
|---|
| 1892 | }
|
|---|
| 1893 | return make_node(AST_Binary, elements[0], {
|
|---|
| 1894 | operator : "+",
|
|---|
| 1895 | left : make_node(AST_String, self, { value: "" }),
|
|---|
| 1896 | right : elements[0]
|
|---|
| 1897 | });
|
|---|
| 1898 | }
|
|---|
| 1899 | if (separator == "") {
|
|---|
| 1900 | var first;
|
|---|
| 1901 | if (elements[0].is_string(compressor)
|
|---|
| 1902 | || elements[1].is_string(compressor)) {
|
|---|
| 1903 | first = elements.shift();
|
|---|
| 1904 | } else {
|
|---|
| 1905 | first = make_node(AST_String, self, { value: "" });
|
|---|
| 1906 | }
|
|---|
| 1907 | return elements.reduce(function(prev, el) {
|
|---|
| 1908 | return make_node(AST_Binary, el, {
|
|---|
| 1909 | operator : "+",
|
|---|
| 1910 | left : prev,
|
|---|
| 1911 | right : el
|
|---|
| 1912 | });
|
|---|
| 1913 | }, first).optimize(compressor);
|
|---|
| 1914 | }
|
|---|
| 1915 | // need this awkward cloning to not affect original element
|
|---|
| 1916 | // best_of will decide which one to get through.
|
|---|
| 1917 | var node = self.clone();
|
|---|
| 1918 | node.expression = node.expression.clone();
|
|---|
| 1919 | node.expression.expression = node.expression.expression.clone();
|
|---|
| 1920 | node.expression.expression.elements = elements;
|
|---|
| 1921 | return best_of(compressor, self, node);
|
|---|
| 1922 | }
|
|---|
| 1923 | break;
|
|---|
| 1924 | case "charAt":
|
|---|
| 1925 | if (exp.expression.is_string(compressor)) {
|
|---|
| 1926 | var arg = self.args[0];
|
|---|
| 1927 | var index = arg ? arg.evaluate(compressor) : 0;
|
|---|
| 1928 | if (index !== arg) {
|
|---|
| 1929 | return make_node(AST_Sub, exp, {
|
|---|
| 1930 | expression: exp.expression,
|
|---|
| 1931 | property: make_node_from_constant(index | 0, arg || exp)
|
|---|
| 1932 | }).optimize(compressor);
|
|---|
| 1933 | }
|
|---|
| 1934 | }
|
|---|
| 1935 | break;
|
|---|
| 1936 | case "apply":
|
|---|
| 1937 | if (self.args.length == 2 && self.args[1] instanceof AST_Array) {
|
|---|
| 1938 | var args = self.args[1].elements.slice();
|
|---|
| 1939 | args.unshift(self.args[0]);
|
|---|
| 1940 | return make_node(AST_Call, self, {
|
|---|
| 1941 | expression: make_node(AST_Dot, exp, {
|
|---|
| 1942 | expression: exp.expression,
|
|---|
| 1943 | optional: false,
|
|---|
| 1944 | property: "call"
|
|---|
| 1945 | }),
|
|---|
| 1946 | args: args
|
|---|
| 1947 | }).optimize(compressor);
|
|---|
| 1948 | }
|
|---|
| 1949 | break;
|
|---|
| 1950 | case "call":
|
|---|
| 1951 | var func = exp.expression;
|
|---|
| 1952 | if (func instanceof AST_SymbolRef) {
|
|---|
| 1953 | func = func.fixed_value();
|
|---|
| 1954 | }
|
|---|
| 1955 | if (func instanceof AST_Lambda && !func.contains_this()) {
|
|---|
| 1956 | return (self.args.length ? make_sequence(this, [
|
|---|
| 1957 | self.args[0],
|
|---|
| 1958 | make_node(AST_Call, self, {
|
|---|
| 1959 | expression: exp.expression,
|
|---|
| 1960 | args: self.args.slice(1)
|
|---|
| 1961 | })
|
|---|
| 1962 | ]) : make_node(AST_Call, self, {
|
|---|
| 1963 | expression: exp.expression,
|
|---|
| 1964 | args: []
|
|---|
| 1965 | })).optimize(compressor);
|
|---|
| 1966 | }
|
|---|
| 1967 | break;
|
|---|
| 1968 | }
|
|---|
| 1969 | }
|
|---|
| 1970 |
|
|---|
| 1971 | if (compressor.option("unsafe_Function")
|
|---|
| 1972 | && is_undeclared_ref(exp)
|
|---|
| 1973 | && exp.name == "Function") {
|
|---|
| 1974 | // new Function() => function(){}
|
|---|
| 1975 | if (self.args.length == 0) return make_empty_function(self).optimize(compressor);
|
|---|
| 1976 | if (self.args.every((x) => x instanceof AST_String)) {
|
|---|
| 1977 | // quite a corner-case, but we can handle it:
|
|---|
| 1978 | // https://github.com/mishoo/UglifyJS2/issues/203
|
|---|
| 1979 | // if the code argument is a constant, then we can minify it.
|
|---|
| 1980 | try {
|
|---|
| 1981 | var code = "n(function(" + self.args.slice(0, -1).map(function(arg) {
|
|---|
| 1982 | return arg.value;
|
|---|
| 1983 | }).join(",") + "){" + self.args[self.args.length - 1].value + "})";
|
|---|
| 1984 | var ast = parse(code);
|
|---|
| 1985 | var mangle = compressor.mangle_options();
|
|---|
| 1986 | ast.figure_out_scope(mangle);
|
|---|
| 1987 | var comp = new Compressor(compressor.options, {
|
|---|
| 1988 | mangle_options: compressor._mangle_options
|
|---|
| 1989 | });
|
|---|
| 1990 | ast = ast.transform(comp);
|
|---|
| 1991 | ast.figure_out_scope(mangle);
|
|---|
| 1992 | ast.compute_char_frequency(mangle);
|
|---|
| 1993 | ast.mangle_names(mangle);
|
|---|
| 1994 | var fun;
|
|---|
| 1995 | walk(ast, node => {
|
|---|
| 1996 | if (is_func_expr(node)) {
|
|---|
| 1997 | fun = node;
|
|---|
| 1998 | return walk_abort;
|
|---|
| 1999 | }
|
|---|
| 2000 | });
|
|---|
| 2001 | var code = OutputStream();
|
|---|
| 2002 | AST_BlockStatement.prototype._codegen.call(fun, fun, code);
|
|---|
| 2003 | self.args = [
|
|---|
| 2004 | make_node(AST_String, self, {
|
|---|
| 2005 | value: fun.argnames.map(function(arg) {
|
|---|
| 2006 | return arg.print_to_string();
|
|---|
| 2007 | }).join(",")
|
|---|
| 2008 | }),
|
|---|
| 2009 | make_node(AST_String, self.args[self.args.length - 1], {
|
|---|
| 2010 | value: code.get().replace(/^{|}$/g, "")
|
|---|
| 2011 | })
|
|---|
| 2012 | ];
|
|---|
| 2013 | return self;
|
|---|
| 2014 | } catch (ex) {
|
|---|
| 2015 | if (!(ex instanceof JS_Parse_Error)) {
|
|---|
| 2016 | throw ex;
|
|---|
| 2017 | }
|
|---|
| 2018 |
|
|---|
| 2019 | // Otherwise, it crashes at runtime. Or maybe it's nonstandard syntax.
|
|---|
| 2020 | }
|
|---|
| 2021 | }
|
|---|
| 2022 | }
|
|---|
| 2023 |
|
|---|
| 2024 | return inline_into_call(self, compressor);
|
|---|
| 2025 | });
|
|---|
| 2026 |
|
|---|
| 2027 | /** Does this node contain optional property access or optional call? */
|
|---|
| 2028 | AST_Node.DEFMETHOD("contains_optional", function() {
|
|---|
| 2029 | if (
|
|---|
| 2030 | this instanceof AST_PropAccess
|
|---|
| 2031 | || this instanceof AST_Call
|
|---|
| 2032 | || this instanceof AST_Chain
|
|---|
| 2033 | ) {
|
|---|
| 2034 | if (this.optional) {
|
|---|
| 2035 | return true;
|
|---|
| 2036 | } else {
|
|---|
| 2037 | return this.expression.contains_optional();
|
|---|
| 2038 | }
|
|---|
| 2039 | } else {
|
|---|
| 2040 | return false;
|
|---|
| 2041 | }
|
|---|
| 2042 | });
|
|---|
| 2043 |
|
|---|
| 2044 | def_optimize(AST_New, function(self, compressor) {
|
|---|
| 2045 | if (
|
|---|
| 2046 | compressor.option("unsafe") &&
|
|---|
| 2047 | is_undeclared_ref(self.expression) &&
|
|---|
| 2048 | ["Object", "RegExp", "Function", "Error", "Array"].includes(self.expression.name)
|
|---|
| 2049 | ) return make_node(AST_Call, self, self).transform(compressor);
|
|---|
| 2050 | return self;
|
|---|
| 2051 | });
|
|---|
| 2052 |
|
|---|
| 2053 | def_optimize(AST_Sequence, function(self, compressor) {
|
|---|
| 2054 | if (!compressor.option("side_effects")) return self;
|
|---|
| 2055 | var expressions = [];
|
|---|
| 2056 | filter_for_side_effects();
|
|---|
| 2057 | var end = expressions.length - 1;
|
|---|
| 2058 | trim_right_for_undefined();
|
|---|
| 2059 | if (end == 0) {
|
|---|
| 2060 | self = maintain_this_binding(compressor.parent(), compressor.self(), expressions[0]);
|
|---|
| 2061 | if (!(self instanceof AST_Sequence)) self = self.optimize(compressor);
|
|---|
| 2062 | return self;
|
|---|
| 2063 | }
|
|---|
| 2064 | self.expressions = expressions;
|
|---|
| 2065 | return self;
|
|---|
| 2066 |
|
|---|
| 2067 | function filter_for_side_effects() {
|
|---|
| 2068 | var first = first_in_statement(compressor);
|
|---|
| 2069 | var last = self.expressions.length - 1;
|
|---|
| 2070 | self.expressions.forEach(function(expr, index) {
|
|---|
| 2071 | if (index < last) expr = expr.drop_side_effect_free(compressor, first);
|
|---|
| 2072 | if (expr) {
|
|---|
| 2073 | merge_sequence(expressions, expr);
|
|---|
| 2074 | first = false;
|
|---|
| 2075 | }
|
|---|
| 2076 | });
|
|---|
| 2077 | }
|
|---|
| 2078 |
|
|---|
| 2079 | function trim_right_for_undefined() {
|
|---|
| 2080 | while (end > 0 && is_undefined(expressions[end], compressor)) end--;
|
|---|
| 2081 | if (end < expressions.length - 1) {
|
|---|
| 2082 | expressions[end] = make_node(AST_UnaryPrefix, self, {
|
|---|
| 2083 | operator : "void",
|
|---|
| 2084 | expression : expressions[end]
|
|---|
| 2085 | });
|
|---|
| 2086 | expressions.length = end + 1;
|
|---|
| 2087 | }
|
|---|
| 2088 | }
|
|---|
| 2089 | });
|
|---|
| 2090 |
|
|---|
| 2091 | AST_Unary.DEFMETHOD("lift_sequences", function(compressor) {
|
|---|
| 2092 | if (compressor.option("sequences")) {
|
|---|
| 2093 | if (this.expression instanceof AST_Sequence) {
|
|---|
| 2094 | var x = this.expression.expressions.slice();
|
|---|
| 2095 | var e = this.clone();
|
|---|
| 2096 | e.expression = x.pop();
|
|---|
| 2097 | x.push(e);
|
|---|
| 2098 | return make_sequence(this, x).optimize(compressor);
|
|---|
| 2099 | }
|
|---|
| 2100 | }
|
|---|
| 2101 | return this;
|
|---|
| 2102 | });
|
|---|
| 2103 |
|
|---|
| 2104 | def_optimize(AST_UnaryPostfix, function(self, compressor) {
|
|---|
| 2105 | return self.lift_sequences(compressor);
|
|---|
| 2106 | });
|
|---|
| 2107 |
|
|---|
| 2108 | def_optimize(AST_UnaryPrefix, function(self, compressor) {
|
|---|
| 2109 | var e = self.expression;
|
|---|
| 2110 | if (
|
|---|
| 2111 | self.operator == "delete" &&
|
|---|
| 2112 | !(
|
|---|
| 2113 | e instanceof AST_SymbolRef ||
|
|---|
| 2114 | e instanceof AST_PropAccess ||
|
|---|
| 2115 | e instanceof AST_Chain ||
|
|---|
| 2116 | is_identifier_atom(e)
|
|---|
| 2117 | )
|
|---|
| 2118 | ) {
|
|---|
| 2119 | return make_sequence(self, [e, make_node(AST_True, self)]).optimize(compressor);
|
|---|
| 2120 | }
|
|---|
| 2121 | // Short-circuit common `void 0`
|
|---|
| 2122 | if (self.operator === "void" && e instanceof AST_Number && e.value === 0) {
|
|---|
| 2123 | return unsafe_undefined_ref(self, compressor) || self;
|
|---|
| 2124 | }
|
|---|
| 2125 | var seq = self.lift_sequences(compressor);
|
|---|
| 2126 | if (seq !== self) {
|
|---|
| 2127 | return seq;
|
|---|
| 2128 | }
|
|---|
| 2129 | if (compressor.option("side_effects") && self.operator == "void") {
|
|---|
| 2130 | e = e.drop_side_effect_free(compressor);
|
|---|
| 2131 | if (e) {
|
|---|
| 2132 | self.expression = e;
|
|---|
| 2133 | return self;
|
|---|
| 2134 | } else {
|
|---|
| 2135 | return make_void_0(self).optimize(compressor);
|
|---|
| 2136 | }
|
|---|
| 2137 | }
|
|---|
| 2138 | if (compressor.in_boolean_context()) {
|
|---|
| 2139 | switch (self.operator) {
|
|---|
| 2140 | case "!":
|
|---|
| 2141 | if (e instanceof AST_UnaryPrefix && e.operator == "!") {
|
|---|
| 2142 | // !!foo ==> foo, if we're in boolean context
|
|---|
| 2143 | return e.expression;
|
|---|
| 2144 | }
|
|---|
| 2145 | if (e instanceof AST_Binary) {
|
|---|
| 2146 | self = best_of(compressor, self, e.negate(compressor, first_in_statement(compressor)));
|
|---|
| 2147 | }
|
|---|
| 2148 | break;
|
|---|
| 2149 | case "typeof":
|
|---|
| 2150 | // typeof always returns a non-empty string, thus it's
|
|---|
| 2151 | // always true in booleans
|
|---|
| 2152 | // And we don't need to check if it's undeclared, because in typeof, that's OK
|
|---|
| 2153 | return (e instanceof AST_SymbolRef ? make_node(AST_True, self) : make_sequence(self, [
|
|---|
| 2154 | e,
|
|---|
| 2155 | make_node(AST_True, self)
|
|---|
| 2156 | ])).optimize(compressor);
|
|---|
| 2157 | }
|
|---|
| 2158 | }
|
|---|
| 2159 | if (self.operator == "-" && e instanceof AST_Infinity) {
|
|---|
| 2160 | e = e.transform(compressor);
|
|---|
| 2161 | }
|
|---|
| 2162 | if (e instanceof AST_Binary
|
|---|
| 2163 | && (self.operator == "+" || self.operator == "-")
|
|---|
| 2164 | && (e.operator == "*" || e.operator == "/" || e.operator == "%")) {
|
|---|
| 2165 | return make_node(AST_Binary, self, {
|
|---|
| 2166 | operator: e.operator,
|
|---|
| 2167 | left: make_node(AST_UnaryPrefix, e.left, {
|
|---|
| 2168 | operator: self.operator,
|
|---|
| 2169 | expression: e.left
|
|---|
| 2170 | }),
|
|---|
| 2171 | right: e.right
|
|---|
| 2172 | });
|
|---|
| 2173 | }
|
|---|
| 2174 |
|
|---|
| 2175 | if (compressor.option("evaluate")) {
|
|---|
| 2176 | // ~~x => x (in 32-bit context)
|
|---|
| 2177 | // ~~{32 bit integer} => {32 bit integer}
|
|---|
| 2178 | if (
|
|---|
| 2179 | self.operator === "~"
|
|---|
| 2180 | && self.expression instanceof AST_UnaryPrefix
|
|---|
| 2181 | && self.expression.operator === "~"
|
|---|
| 2182 | && (compressor.in_32_bit_context(false) || self.expression.expression.is_32_bit_integer(compressor))
|
|---|
| 2183 | ) {
|
|---|
| 2184 | return self.expression.expression;
|
|---|
| 2185 | }
|
|---|
| 2186 |
|
|---|
| 2187 | // ~(x ^ y) => x ^ ~y
|
|---|
| 2188 | if (
|
|---|
| 2189 | self.operator === "~"
|
|---|
| 2190 | && e instanceof AST_Binary
|
|---|
| 2191 | && e.operator === "^"
|
|---|
| 2192 | ) {
|
|---|
| 2193 | if (e.left instanceof AST_UnaryPrefix && e.left.operator === "~") {
|
|---|
| 2194 | // ~(~x ^ y) => x ^ y
|
|---|
| 2195 | e.left = e.left.bitwise_negate(compressor, true);
|
|---|
| 2196 | } else {
|
|---|
| 2197 | e.right = e.right.bitwise_negate(compressor, true);
|
|---|
| 2198 | }
|
|---|
| 2199 | return e;
|
|---|
| 2200 | }
|
|---|
| 2201 | }
|
|---|
| 2202 |
|
|---|
| 2203 | if (
|
|---|
| 2204 | self.operator != "-"
|
|---|
| 2205 | // avoid infinite recursion of numerals
|
|---|
| 2206 | || !(e instanceof AST_Number || e instanceof AST_Infinity || e instanceof AST_BigInt)
|
|---|
| 2207 | ) {
|
|---|
| 2208 | var ev = self.evaluate(compressor);
|
|---|
| 2209 | if (ev !== self) {
|
|---|
| 2210 | ev = make_node_from_constant(ev, self).optimize(compressor);
|
|---|
| 2211 | return best_of(compressor, ev, self);
|
|---|
| 2212 | }
|
|---|
| 2213 | }
|
|---|
| 2214 | return self;
|
|---|
| 2215 | });
|
|---|
| 2216 |
|
|---|
| 2217 | AST_Binary.DEFMETHOD("lift_sequences", function(compressor) {
|
|---|
| 2218 | if (compressor.option("sequences")) {
|
|---|
| 2219 | if (this.left instanceof AST_Sequence) {
|
|---|
| 2220 | var x = this.left.expressions.slice();
|
|---|
| 2221 | var e = this.clone();
|
|---|
| 2222 | e.left = x.pop();
|
|---|
| 2223 | x.push(e);
|
|---|
| 2224 | return make_sequence(this, x).optimize(compressor);
|
|---|
| 2225 | }
|
|---|
| 2226 | if (this.right instanceof AST_Sequence && !this.left.has_side_effects(compressor)) {
|
|---|
| 2227 | var assign = this.operator == "=" && this.left instanceof AST_SymbolRef;
|
|---|
| 2228 | var x = this.right.expressions;
|
|---|
| 2229 | var last = x.length - 1;
|
|---|
| 2230 | for (var i = 0; i < last; i++) {
|
|---|
| 2231 | if (!assign && x[i].has_side_effects(compressor)) break;
|
|---|
| 2232 | }
|
|---|
| 2233 | if (i == last) {
|
|---|
| 2234 | x = x.slice();
|
|---|
| 2235 | var e = this.clone();
|
|---|
| 2236 | e.right = x.pop();
|
|---|
| 2237 | x.push(e);
|
|---|
| 2238 | return make_sequence(this, x).optimize(compressor);
|
|---|
| 2239 | } else if (i > 0) {
|
|---|
| 2240 | var e = this.clone();
|
|---|
| 2241 | e.right = make_sequence(this.right, x.slice(i));
|
|---|
| 2242 | x = x.slice(0, i);
|
|---|
| 2243 | x.push(e);
|
|---|
| 2244 | return make_sequence(this, x).optimize(compressor);
|
|---|
| 2245 | }
|
|---|
| 2246 | }
|
|---|
| 2247 | }
|
|---|
| 2248 | return this;
|
|---|
| 2249 | });
|
|---|
| 2250 |
|
|---|
| 2251 | var commutativeOperators = makePredicate("== === != !== * & | ^");
|
|---|
| 2252 | function is_object(node) {
|
|---|
| 2253 | return node instanceof AST_Array
|
|---|
| 2254 | || node instanceof AST_Lambda
|
|---|
| 2255 | || node instanceof AST_Object
|
|---|
| 2256 | || node instanceof AST_Class;
|
|---|
| 2257 | }
|
|---|
| 2258 |
|
|---|
| 2259 | def_optimize(AST_Binary, function(self, compressor) {
|
|---|
| 2260 | function reversible() {
|
|---|
| 2261 | return self.left.is_constant()
|
|---|
| 2262 | || self.right.is_constant()
|
|---|
| 2263 | || !self.left.has_side_effects(compressor)
|
|---|
| 2264 | && !self.right.has_side_effects(compressor);
|
|---|
| 2265 | }
|
|---|
| 2266 | function reverse(op) {
|
|---|
| 2267 | if (reversible()) {
|
|---|
| 2268 | if (op) self.operator = op;
|
|---|
| 2269 | var tmp = self.left;
|
|---|
| 2270 | self.left = self.right;
|
|---|
| 2271 | self.right = tmp;
|
|---|
| 2272 | }
|
|---|
| 2273 | }
|
|---|
| 2274 | if (compressor.option("lhs_constants") && commutativeOperators.has(self.operator)) {
|
|---|
| 2275 | if (self.right.is_constant()
|
|---|
| 2276 | && !self.left.is_constant()) {
|
|---|
| 2277 | // if right is a constant, whatever side effects the
|
|---|
| 2278 | // left side might have could not influence the
|
|---|
| 2279 | // result. hence, force switch.
|
|---|
| 2280 |
|
|---|
| 2281 | if (!(self.left instanceof AST_Binary
|
|---|
| 2282 | && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
|
|---|
| 2283 | reverse();
|
|---|
| 2284 | }
|
|---|
| 2285 | }
|
|---|
| 2286 | }
|
|---|
| 2287 | self = self.lift_sequences(compressor);
|
|---|
| 2288 | if (compressor.option("comparisons")) switch (self.operator) {
|
|---|
| 2289 | case "===":
|
|---|
| 2290 | case "!==":
|
|---|
| 2291 | var is_strict_comparison = true;
|
|---|
| 2292 | if (
|
|---|
| 2293 | (self.left.is_string(compressor) && self.right.is_string(compressor)) ||
|
|---|
| 2294 | (self.left.is_number(compressor) && self.right.is_number(compressor)) ||
|
|---|
| 2295 | (self.left.is_bigint(compressor) && self.right.is_bigint(compressor)) ||
|
|---|
| 2296 | (self.left.is_boolean() && self.right.is_boolean()) ||
|
|---|
| 2297 | self.left.equivalent_to(self.right)
|
|---|
| 2298 | ) {
|
|---|
| 2299 | self.operator = self.operator.substr(0, 2);
|
|---|
| 2300 | }
|
|---|
| 2301 |
|
|---|
| 2302 | // XXX: intentionally falling down to the next case
|
|---|
| 2303 | case "==":
|
|---|
| 2304 | case "!=":
|
|---|
| 2305 | // void 0 == x => null == x
|
|---|
| 2306 | if (!is_strict_comparison && is_undefined(self.left, compressor)) {
|
|---|
| 2307 | self.left = make_node(AST_Null, self.left);
|
|---|
| 2308 | // x == void 0 => x == null
|
|---|
| 2309 | } else if (!is_strict_comparison && is_undefined(self.right, compressor)) {
|
|---|
| 2310 | self.right = make_node(AST_Null, self.right);
|
|---|
| 2311 | } else if (compressor.option("typeofs")
|
|---|
| 2312 | // "undefined" == typeof x => undefined === x
|
|---|
| 2313 | && self.left instanceof AST_String
|
|---|
| 2314 | && self.left.value == "undefined"
|
|---|
| 2315 | && self.right instanceof AST_UnaryPrefix
|
|---|
| 2316 | && self.right.operator == "typeof") {
|
|---|
| 2317 | var expr = self.right.expression;
|
|---|
| 2318 | if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
|
|---|
| 2319 | : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
|
|---|
| 2320 | self.right = expr;
|
|---|
| 2321 | self.left = make_void_0(self.left).optimize(compressor);
|
|---|
| 2322 | if (self.operator.length == 2) self.operator += "=";
|
|---|
| 2323 | }
|
|---|
| 2324 | } else if (compressor.option("typeofs")
|
|---|
| 2325 | // typeof x === "undefined" => x === undefined
|
|---|
| 2326 | && self.left instanceof AST_UnaryPrefix
|
|---|
| 2327 | && self.left.operator == "typeof"
|
|---|
| 2328 | && self.right instanceof AST_String
|
|---|
| 2329 | && self.right.value == "undefined") {
|
|---|
| 2330 | var expr = self.left.expression;
|
|---|
| 2331 | if (expr instanceof AST_SymbolRef ? expr.is_declared(compressor)
|
|---|
| 2332 | : !(expr instanceof AST_PropAccess && compressor.option("ie8"))) {
|
|---|
| 2333 | self.left = expr;
|
|---|
| 2334 | self.right = make_void_0(self.right).optimize(compressor);
|
|---|
| 2335 | if (self.operator.length == 2) self.operator += "=";
|
|---|
| 2336 | }
|
|---|
| 2337 | } else if (self.left instanceof AST_SymbolRef
|
|---|
| 2338 | // obj !== obj => false
|
|---|
| 2339 | && self.right instanceof AST_SymbolRef
|
|---|
| 2340 | && self.left.definition() === self.right.definition()
|
|---|
| 2341 | && is_object(self.left.fixed_value())) {
|
|---|
| 2342 | return make_node(self.operator[0] == "=" ? AST_True : AST_False, self);
|
|---|
| 2343 | } else if (self.left.is_32_bit_integer(compressor) && self.right.is_32_bit_integer(compressor)) {
|
|---|
| 2344 | const not = node => make_node(AST_UnaryPrefix, node, {
|
|---|
| 2345 | operator: "!",
|
|---|
| 2346 | expression: node
|
|---|
| 2347 | });
|
|---|
| 2348 | const booleanify = (node, truthy) => {
|
|---|
| 2349 | if (truthy) {
|
|---|
| 2350 | return compressor.in_boolean_context()
|
|---|
| 2351 | ? node
|
|---|
| 2352 | : not(not(node));
|
|---|
| 2353 | } else {
|
|---|
| 2354 | return not(node);
|
|---|
| 2355 | }
|
|---|
| 2356 | };
|
|---|
| 2357 |
|
|---|
| 2358 | // The only falsy 32-bit integer is 0
|
|---|
| 2359 | if (self.left instanceof AST_Number && self.left.value === 0) {
|
|---|
| 2360 | return booleanify(self.right, self.operator[0] === "!");
|
|---|
| 2361 | }
|
|---|
| 2362 | if (self.right instanceof AST_Number && self.right.value === 0) {
|
|---|
| 2363 | return booleanify(self.left, self.operator[0] === "!");
|
|---|
| 2364 | }
|
|---|
| 2365 |
|
|---|
| 2366 | // Mask all-bits check
|
|---|
| 2367 | // (x & 0xFF) != 0xFF => !(~x & 0xFF)
|
|---|
| 2368 | let and_op, x, mask;
|
|---|
| 2369 | if (
|
|---|
| 2370 | (and_op =
|
|---|
| 2371 | self.left instanceof AST_Binary ? self.left
|
|---|
| 2372 | : self.right instanceof AST_Binary ? self.right : null)
|
|---|
| 2373 | && (mask = and_op === self.left ? self.right : self.left)
|
|---|
| 2374 | && and_op.operator === "&"
|
|---|
| 2375 | && mask instanceof AST_Number
|
|---|
| 2376 | && mask.is_32_bit_integer(compressor)
|
|---|
| 2377 | && (x =
|
|---|
| 2378 | and_op.left.equivalent_to(mask) ? and_op.right
|
|---|
| 2379 | : and_op.right.equivalent_to(mask) ? and_op.left : null)
|
|---|
| 2380 | ) {
|
|---|
| 2381 | let optimized = booleanify(make_node(AST_Binary, self, {
|
|---|
| 2382 | operator: "&",
|
|---|
| 2383 | left: mask,
|
|---|
| 2384 | right: make_node(AST_UnaryPrefix, self, {
|
|---|
| 2385 | operator: "~",
|
|---|
| 2386 | expression: x
|
|---|
| 2387 | })
|
|---|
| 2388 | }), self.operator[0] === "!");
|
|---|
| 2389 |
|
|---|
| 2390 | return best_of(compressor, optimized, self);
|
|---|
| 2391 | }
|
|---|
| 2392 | }
|
|---|
| 2393 | break;
|
|---|
| 2394 | case "&&":
|
|---|
| 2395 | case "||":
|
|---|
| 2396 | var lhs = self.left;
|
|---|
| 2397 | if (lhs.operator == self.operator) {
|
|---|
| 2398 | lhs = lhs.right;
|
|---|
| 2399 | }
|
|---|
| 2400 | if (lhs instanceof AST_Binary
|
|---|
| 2401 | && lhs.operator == (self.operator == "&&" ? "!==" : "===")
|
|---|
| 2402 | && self.right instanceof AST_Binary
|
|---|
| 2403 | && lhs.operator == self.right.operator
|
|---|
| 2404 | && (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null
|
|---|
| 2405 | || lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor))
|
|---|
| 2406 | && !lhs.right.has_side_effects(compressor)
|
|---|
| 2407 | && lhs.right.equivalent_to(self.right.right)) {
|
|---|
| 2408 | var combined = make_node(AST_Binary, self, {
|
|---|
| 2409 | operator: lhs.operator.slice(0, -1),
|
|---|
| 2410 | left: make_node(AST_Null, self),
|
|---|
| 2411 | right: lhs.right
|
|---|
| 2412 | });
|
|---|
| 2413 | if (lhs !== self.left) {
|
|---|
| 2414 | combined = make_node(AST_Binary, self, {
|
|---|
| 2415 | operator: self.operator,
|
|---|
| 2416 | left: self.left.left,
|
|---|
| 2417 | right: combined
|
|---|
| 2418 | });
|
|---|
| 2419 | }
|
|---|
| 2420 | return combined;
|
|---|
| 2421 | }
|
|---|
| 2422 | break;
|
|---|
| 2423 | }
|
|---|
| 2424 | if (self.operator == "+" && compressor.in_boolean_context()) {
|
|---|
| 2425 | var ll = self.left.evaluate(compressor);
|
|---|
| 2426 | var rr = self.right.evaluate(compressor);
|
|---|
| 2427 | if (ll && typeof ll == "string") {
|
|---|
| 2428 | return make_sequence(self, [
|
|---|
| 2429 | self.right,
|
|---|
| 2430 | make_node(AST_True, self)
|
|---|
| 2431 | ]).optimize(compressor);
|
|---|
| 2432 | }
|
|---|
| 2433 | if (rr && typeof rr == "string") {
|
|---|
| 2434 | return make_sequence(self, [
|
|---|
| 2435 | self.left,
|
|---|
| 2436 | make_node(AST_True, self)
|
|---|
| 2437 | ]).optimize(compressor);
|
|---|
| 2438 | }
|
|---|
| 2439 | }
|
|---|
| 2440 | if (compressor.option("comparisons") && self.is_boolean()) {
|
|---|
| 2441 | if (!(compressor.parent() instanceof AST_Binary)
|
|---|
| 2442 | || compressor.parent() instanceof AST_Assign) {
|
|---|
| 2443 | var negated = make_node(AST_UnaryPrefix, self, {
|
|---|
| 2444 | operator: "!",
|
|---|
| 2445 | expression: self.negate(compressor, first_in_statement(compressor))
|
|---|
| 2446 | });
|
|---|
| 2447 | self = best_of(compressor, self, negated);
|
|---|
| 2448 | }
|
|---|
| 2449 | if (compressor.option("unsafe_comps")) {
|
|---|
| 2450 | switch (self.operator) {
|
|---|
| 2451 | case "<": reverse(">"); break;
|
|---|
| 2452 | case "<=": reverse(">="); break;
|
|---|
| 2453 | }
|
|---|
| 2454 | }
|
|---|
| 2455 | }
|
|---|
| 2456 | if (self.operator == "+") {
|
|---|
| 2457 | if (self.right instanceof AST_String
|
|---|
| 2458 | && self.right.getValue() == ""
|
|---|
| 2459 | && self.left.is_string(compressor)) {
|
|---|
| 2460 | return self.left;
|
|---|
| 2461 | }
|
|---|
| 2462 | if (self.left instanceof AST_String
|
|---|
| 2463 | && self.left.getValue() == ""
|
|---|
| 2464 | && self.right.is_string(compressor)) {
|
|---|
| 2465 | return self.right;
|
|---|
| 2466 | }
|
|---|
| 2467 | if (self.left instanceof AST_Binary
|
|---|
| 2468 | && self.left.operator == "+"
|
|---|
| 2469 | && self.left.left instanceof AST_String
|
|---|
| 2470 | && self.left.left.getValue() == ""
|
|---|
| 2471 | && self.right.is_string(compressor)) {
|
|---|
| 2472 | self.left = self.left.right;
|
|---|
| 2473 | return self;
|
|---|
| 2474 | }
|
|---|
| 2475 | }
|
|---|
| 2476 | if (compressor.option("evaluate")) {
|
|---|
| 2477 | switch (self.operator) {
|
|---|
| 2478 | case "&&":
|
|---|
| 2479 | var ll = has_flag(self.left, TRUTHY)
|
|---|
| 2480 | ? true
|
|---|
| 2481 | : has_flag(self.left, FALSY)
|
|---|
| 2482 | ? false
|
|---|
| 2483 | : self.left.evaluate(compressor);
|
|---|
| 2484 | if (!ll) {
|
|---|
| 2485 | return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
|
|---|
| 2486 | } else if (!(ll instanceof AST_Node)) {
|
|---|
| 2487 | return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
|
|---|
| 2488 | }
|
|---|
| 2489 | var rr = self.right.evaluate(compressor);
|
|---|
| 2490 | if (!rr) {
|
|---|
| 2491 | if (compressor.in_boolean_context()) {
|
|---|
| 2492 | return make_sequence(self, [
|
|---|
| 2493 | self.left,
|
|---|
| 2494 | make_node(AST_False, self)
|
|---|
| 2495 | ]).optimize(compressor);
|
|---|
| 2496 | } else {
|
|---|
| 2497 | set_flag(self, FALSY);
|
|---|
| 2498 | }
|
|---|
| 2499 | } else if (!(rr instanceof AST_Node)) {
|
|---|
| 2500 | var parent = compressor.parent();
|
|---|
| 2501 | if (parent.operator == "&&" && parent.left === compressor.self() || compressor.in_boolean_context()) {
|
|---|
| 2502 | return self.left.optimize(compressor);
|
|---|
| 2503 | }
|
|---|
| 2504 | }
|
|---|
| 2505 | // x || false && y ---> x ? y : false
|
|---|
| 2506 | if (self.left.operator == "||") {
|
|---|
| 2507 | var lr = self.left.right.evaluate(compressor);
|
|---|
| 2508 | if (!lr) return make_node(AST_Conditional, self, {
|
|---|
| 2509 | condition: self.left.left,
|
|---|
| 2510 | consequent: self.right,
|
|---|
| 2511 | alternative: self.left.right
|
|---|
| 2512 | }).optimize(compressor);
|
|---|
| 2513 | }
|
|---|
| 2514 | break;
|
|---|
| 2515 | case "||":
|
|---|
| 2516 | var ll = has_flag(self.left, TRUTHY)
|
|---|
| 2517 | ? true
|
|---|
| 2518 | : has_flag(self.left, FALSY)
|
|---|
| 2519 | ? false
|
|---|
| 2520 | : self.left.evaluate(compressor);
|
|---|
| 2521 | if (!ll) {
|
|---|
| 2522 | return make_sequence(self, [ self.left, self.right ]).optimize(compressor);
|
|---|
| 2523 | } else if (!(ll instanceof AST_Node)) {
|
|---|
| 2524 | return maintain_this_binding(compressor.parent(), compressor.self(), self.left).optimize(compressor);
|
|---|
| 2525 | }
|
|---|
| 2526 | var rr = self.right.evaluate(compressor);
|
|---|
| 2527 | if (!rr) {
|
|---|
| 2528 | var parent = compressor.parent();
|
|---|
| 2529 | if (parent.operator == "||" && parent.left === compressor.self() || compressor.in_boolean_context()) {
|
|---|
| 2530 | return self.left.optimize(compressor);
|
|---|
| 2531 | }
|
|---|
| 2532 | } else if (!(rr instanceof AST_Node)) {
|
|---|
| 2533 | if (compressor.in_boolean_context()) {
|
|---|
| 2534 | return make_sequence(self, [
|
|---|
| 2535 | self.left,
|
|---|
| 2536 | make_node(AST_True, self)
|
|---|
| 2537 | ]).optimize(compressor);
|
|---|
| 2538 | } else {
|
|---|
| 2539 | set_flag(self, TRUTHY);
|
|---|
| 2540 | }
|
|---|
| 2541 | }
|
|---|
| 2542 | if (self.left.operator == "&&") {
|
|---|
| 2543 | var lr = self.left.right.evaluate(compressor);
|
|---|
| 2544 | if (lr && !(lr instanceof AST_Node)) return make_node(AST_Conditional, self, {
|
|---|
| 2545 | condition: self.left.left,
|
|---|
| 2546 | consequent: self.left.right,
|
|---|
| 2547 | alternative: self.right
|
|---|
| 2548 | }).optimize(compressor);
|
|---|
| 2549 | }
|
|---|
| 2550 | break;
|
|---|
| 2551 | case "??":
|
|---|
| 2552 | if (is_nullish(self.left, compressor)) {
|
|---|
| 2553 | return self.right;
|
|---|
| 2554 | }
|
|---|
| 2555 |
|
|---|
| 2556 | var ll = self.left.evaluate(compressor);
|
|---|
| 2557 | if (!(ll instanceof AST_Node)) {
|
|---|
| 2558 | // if we know the value for sure we can simply compute right away.
|
|---|
| 2559 | return ll == null ? self.right : self.left;
|
|---|
| 2560 | }
|
|---|
| 2561 |
|
|---|
| 2562 | if (compressor.in_boolean_context()) {
|
|---|
| 2563 | const rr = self.right.evaluate(compressor);
|
|---|
| 2564 | if (!(rr instanceof AST_Node) && !rr) {
|
|---|
| 2565 | return self.left;
|
|---|
| 2566 | }
|
|---|
| 2567 | }
|
|---|
| 2568 | }
|
|---|
| 2569 | var associative = true;
|
|---|
| 2570 | switch (self.operator) {
|
|---|
| 2571 | case "+":
|
|---|
| 2572 | // (x + "foo") + "bar" => x + "foobar"
|
|---|
| 2573 | if (self.right instanceof AST_Constant
|
|---|
| 2574 | && self.left instanceof AST_Binary
|
|---|
| 2575 | && self.left.operator == "+"
|
|---|
| 2576 | && self.left.is_string(compressor)) {
|
|---|
| 2577 | var binary = make_node(AST_Binary, self, {
|
|---|
| 2578 | operator: "+",
|
|---|
| 2579 | left: self.left.right,
|
|---|
| 2580 | right: self.right,
|
|---|
| 2581 | });
|
|---|
| 2582 | var r = binary.optimize(compressor);
|
|---|
| 2583 | if (binary !== r) {
|
|---|
| 2584 | self = make_node(AST_Binary, self, {
|
|---|
| 2585 | operator: "+",
|
|---|
| 2586 | left: self.left.left,
|
|---|
| 2587 | right: r
|
|---|
| 2588 | });
|
|---|
| 2589 | }
|
|---|
| 2590 | }
|
|---|
| 2591 | // (x + "foo") + ("bar" + y) => (x + "foobar") + y
|
|---|
| 2592 | if (self.left instanceof AST_Binary
|
|---|
| 2593 | && self.left.operator == "+"
|
|---|
| 2594 | && self.left.is_string(compressor)
|
|---|
| 2595 | && self.right instanceof AST_Binary
|
|---|
| 2596 | && self.right.operator == "+"
|
|---|
| 2597 | && self.right.is_string(compressor)) {
|
|---|
| 2598 | var binary = make_node(AST_Binary, self, {
|
|---|
| 2599 | operator: "+",
|
|---|
| 2600 | left: self.left.right,
|
|---|
| 2601 | right: self.right.left,
|
|---|
| 2602 | });
|
|---|
| 2603 | var m = binary.optimize(compressor);
|
|---|
| 2604 | if (binary !== m) {
|
|---|
| 2605 | self = make_node(AST_Binary, self, {
|
|---|
| 2606 | operator: "+",
|
|---|
| 2607 | left: make_node(AST_Binary, self.left, {
|
|---|
| 2608 | operator: "+",
|
|---|
| 2609 | left: self.left.left,
|
|---|
| 2610 | right: m
|
|---|
| 2611 | }),
|
|---|
| 2612 | right: self.right.right
|
|---|
| 2613 | });
|
|---|
| 2614 | }
|
|---|
| 2615 | }
|
|---|
| 2616 | // a + -b => a - b
|
|---|
| 2617 | if (self.right instanceof AST_UnaryPrefix
|
|---|
| 2618 | && self.right.operator == "-"
|
|---|
| 2619 | && self.left.is_number_or_bigint(compressor)) {
|
|---|
| 2620 | self = make_node(AST_Binary, self, {
|
|---|
| 2621 | operator: "-",
|
|---|
| 2622 | left: self.left,
|
|---|
| 2623 | right: self.right.expression
|
|---|
| 2624 | });
|
|---|
| 2625 | break;
|
|---|
| 2626 | }
|
|---|
| 2627 | // -a + b => b - a
|
|---|
| 2628 | if (self.left instanceof AST_UnaryPrefix
|
|---|
| 2629 | && self.left.operator == "-"
|
|---|
| 2630 | && reversible()
|
|---|
| 2631 | && self.right.is_number_or_bigint(compressor)) {
|
|---|
| 2632 | self = make_node(AST_Binary, self, {
|
|---|
| 2633 | operator: "-",
|
|---|
| 2634 | left: self.right,
|
|---|
| 2635 | right: self.left.expression
|
|---|
| 2636 | });
|
|---|
| 2637 | break;
|
|---|
| 2638 | }
|
|---|
| 2639 | // `foo${bar}baz` + 1 => `foo${bar}baz1`
|
|---|
| 2640 | if (self.left instanceof AST_TemplateString) {
|
|---|
| 2641 | var l = self.left;
|
|---|
| 2642 | var r = self.right.evaluate(compressor);
|
|---|
| 2643 | if (r != self.right) {
|
|---|
| 2644 | l.segments[l.segments.length - 1].value += String(r);
|
|---|
| 2645 | return l;
|
|---|
| 2646 | }
|
|---|
| 2647 | }
|
|---|
| 2648 | // 1 + `foo${bar}baz` => `1foo${bar}baz`
|
|---|
| 2649 | if (self.right instanceof AST_TemplateString) {
|
|---|
| 2650 | var r = self.right;
|
|---|
| 2651 | var l = self.left.evaluate(compressor);
|
|---|
| 2652 | if (l != self.left) {
|
|---|
| 2653 | r.segments[0].value = String(l) + r.segments[0].value;
|
|---|
| 2654 | return r;
|
|---|
| 2655 | }
|
|---|
| 2656 | }
|
|---|
| 2657 | // `1${bar}2` + `foo${bar}baz` => `1${bar}2foo${bar}baz`
|
|---|
| 2658 | if (self.left instanceof AST_TemplateString
|
|---|
| 2659 | && self.right instanceof AST_TemplateString) {
|
|---|
| 2660 | var l = self.left;
|
|---|
| 2661 | var segments = l.segments;
|
|---|
| 2662 | var r = self.right;
|
|---|
| 2663 | segments[segments.length - 1].value += r.segments[0].value;
|
|---|
| 2664 | for (var i = 1; i < r.segments.length; i++) {
|
|---|
| 2665 | segments.push(r.segments[i]);
|
|---|
| 2666 | }
|
|---|
| 2667 | return l;
|
|---|
| 2668 | }
|
|---|
| 2669 | case "*":
|
|---|
| 2670 | associative = compressor.option("unsafe_math");
|
|---|
| 2671 | case "&":
|
|---|
| 2672 | case "|":
|
|---|
| 2673 | case "^":
|
|---|
| 2674 | // a + +b => +b + a
|
|---|
| 2675 | if (
|
|---|
| 2676 | self.left.is_number_or_bigint(compressor)
|
|---|
| 2677 | && self.right.is_number_or_bigint(compressor)
|
|---|
| 2678 | && reversible()
|
|---|
| 2679 | && !(self.left instanceof AST_Binary
|
|---|
| 2680 | && self.left.operator != self.operator
|
|---|
| 2681 | && PRECEDENCE[self.left.operator] >= PRECEDENCE[self.operator])) {
|
|---|
| 2682 | var reversed = make_node(AST_Binary, self, {
|
|---|
| 2683 | operator: self.operator,
|
|---|
| 2684 | left: self.right,
|
|---|
| 2685 | right: self.left
|
|---|
| 2686 | });
|
|---|
| 2687 | if (self.right instanceof AST_Constant
|
|---|
| 2688 | && !(self.left instanceof AST_Constant)) {
|
|---|
| 2689 | self = best_of(compressor, reversed, self);
|
|---|
| 2690 | } else {
|
|---|
| 2691 | self = best_of(compressor, self, reversed);
|
|---|
| 2692 | }
|
|---|
| 2693 | }
|
|---|
| 2694 | if (associative && self.is_number_or_bigint(compressor)) {
|
|---|
| 2695 | // a + (b + c) => (a + b) + c
|
|---|
| 2696 | if (self.right instanceof AST_Binary
|
|---|
| 2697 | && self.right.operator == self.operator) {
|
|---|
| 2698 | self = make_node(AST_Binary, self, {
|
|---|
| 2699 | operator: self.operator,
|
|---|
| 2700 | left: make_node(AST_Binary, self.left, {
|
|---|
| 2701 | operator: self.operator,
|
|---|
| 2702 | left: self.left,
|
|---|
| 2703 | right: self.right.left,
|
|---|
| 2704 | start: self.left.start,
|
|---|
| 2705 | end: self.right.left.end
|
|---|
| 2706 | }),
|
|---|
| 2707 | right: self.right.right
|
|---|
| 2708 | });
|
|---|
| 2709 | }
|
|---|
| 2710 | // (n + 2) + 3 => 5 + n
|
|---|
| 2711 | // (2 * n) * 3 => 6 + n
|
|---|
| 2712 | if (self.right instanceof AST_Constant
|
|---|
| 2713 | && self.left instanceof AST_Binary
|
|---|
| 2714 | && self.left.operator == self.operator) {
|
|---|
| 2715 | if (self.left.left instanceof AST_Constant) {
|
|---|
| 2716 | self = make_node(AST_Binary, self, {
|
|---|
| 2717 | operator: self.operator,
|
|---|
| 2718 | left: make_node(AST_Binary, self.left, {
|
|---|
| 2719 | operator: self.operator,
|
|---|
| 2720 | left: self.left.left,
|
|---|
| 2721 | right: self.right,
|
|---|
| 2722 | start: self.left.left.start,
|
|---|
| 2723 | end: self.right.end
|
|---|
| 2724 | }),
|
|---|
| 2725 | right: self.left.right
|
|---|
| 2726 | });
|
|---|
| 2727 | } else if (self.left.right instanceof AST_Constant) {
|
|---|
| 2728 | self = make_node(AST_Binary, self, {
|
|---|
| 2729 | operator: self.operator,
|
|---|
| 2730 | left: make_node(AST_Binary, self.left, {
|
|---|
| 2731 | operator: self.operator,
|
|---|
| 2732 | left: self.left.right,
|
|---|
| 2733 | right: self.right,
|
|---|
| 2734 | start: self.left.right.start,
|
|---|
| 2735 | end: self.right.end
|
|---|
| 2736 | }),
|
|---|
| 2737 | right: self.left.left
|
|---|
| 2738 | });
|
|---|
| 2739 | }
|
|---|
| 2740 | }
|
|---|
| 2741 | // (a | 1) | (2 | d) => (3 | a) | b
|
|---|
| 2742 | if (self.left instanceof AST_Binary
|
|---|
| 2743 | && self.left.operator == self.operator
|
|---|
| 2744 | && self.left.right instanceof AST_Constant
|
|---|
| 2745 | && self.right instanceof AST_Binary
|
|---|
| 2746 | && self.right.operator == self.operator
|
|---|
| 2747 | && self.right.left instanceof AST_Constant) {
|
|---|
| 2748 | self = make_node(AST_Binary, self, {
|
|---|
| 2749 | operator: self.operator,
|
|---|
| 2750 | left: make_node(AST_Binary, self.left, {
|
|---|
| 2751 | operator: self.operator,
|
|---|
| 2752 | left: make_node(AST_Binary, self.left.left, {
|
|---|
| 2753 | operator: self.operator,
|
|---|
| 2754 | left: self.left.right,
|
|---|
| 2755 | right: self.right.left,
|
|---|
| 2756 | start: self.left.right.start,
|
|---|
| 2757 | end: self.right.left.end
|
|---|
| 2758 | }),
|
|---|
| 2759 | right: self.left.left
|
|---|
| 2760 | }),
|
|---|
| 2761 | right: self.right.right
|
|---|
| 2762 | });
|
|---|
| 2763 | }
|
|---|
| 2764 | }
|
|---|
| 2765 | }
|
|---|
| 2766 |
|
|---|
| 2767 | // bitwise ops
|
|---|
| 2768 | if (bitwise_binop.has(self.operator)) {
|
|---|
| 2769 | // Use De Morgan's laws
|
|---|
| 2770 | // z & (X | y)
|
|---|
| 2771 | // => z & X (given y & z === 0)
|
|---|
| 2772 | // => z & X | {y & z} (given y & z !== 0)
|
|---|
| 2773 | let y, z, x_node, y_node, z_node = self.left;
|
|---|
| 2774 | if (
|
|---|
| 2775 | self.operator === "&"
|
|---|
| 2776 | && self.right instanceof AST_Binary
|
|---|
| 2777 | && self.right.operator === "|"
|
|---|
| 2778 | && typeof (z = self.left.evaluate(compressor)) === "number"
|
|---|
| 2779 | ) {
|
|---|
| 2780 | if (typeof (y = self.right.right.evaluate(compressor)) === "number") {
|
|---|
| 2781 | // z & (X | y)
|
|---|
| 2782 | x_node = self.right.left;
|
|---|
| 2783 | y_node = self.right.right;
|
|---|
| 2784 | } else if (typeof (y = self.right.left.evaluate(compressor)) === "number") {
|
|---|
| 2785 | // z & (y | X)
|
|---|
| 2786 | x_node = self.right.right;
|
|---|
| 2787 | y_node = self.right.left;
|
|---|
| 2788 | }
|
|---|
| 2789 |
|
|---|
| 2790 | if (x_node && y_node) {
|
|---|
| 2791 | if ((y & z) === 0) {
|
|---|
| 2792 | self = make_node(AST_Binary, self, {
|
|---|
| 2793 | operator: self.operator,
|
|---|
| 2794 | left: z_node,
|
|---|
| 2795 | right: x_node
|
|---|
| 2796 | });
|
|---|
| 2797 | } else {
|
|---|
| 2798 | const reordered_ops = make_node(AST_Binary, self, {
|
|---|
| 2799 | operator: "|",
|
|---|
| 2800 | left: make_node(AST_Binary, self, {
|
|---|
| 2801 | operator: "&",
|
|---|
| 2802 | left: x_node,
|
|---|
| 2803 | right: z_node
|
|---|
| 2804 | }),
|
|---|
| 2805 | right: make_node_from_constant(y & z, y_node),
|
|---|
| 2806 | });
|
|---|
| 2807 |
|
|---|
| 2808 | self = best_of(compressor, self, reordered_ops);
|
|---|
| 2809 | }
|
|---|
| 2810 | }
|
|---|
| 2811 | }
|
|---|
| 2812 |
|
|---|
| 2813 | // x | x => 0 | x
|
|---|
| 2814 | // x & x => 0 | x
|
|---|
| 2815 | if (
|
|---|
| 2816 | (self.operator === "|" || self.operator === "&")
|
|---|
| 2817 | && self.left.equivalent_to(self.right)
|
|---|
| 2818 | && !self.left.has_side_effects(compressor)
|
|---|
| 2819 | && compressor.in_32_bit_context(true)
|
|---|
| 2820 | ) {
|
|---|
| 2821 | self.left = make_node(AST_Number, self, { value: 0 });
|
|---|
| 2822 | self.operator = "|";
|
|---|
| 2823 | }
|
|---|
| 2824 |
|
|---|
| 2825 | // ~x ^ ~y => x ^ y
|
|---|
| 2826 | if (
|
|---|
| 2827 | self.operator === "^"
|
|---|
| 2828 | && self.left instanceof AST_UnaryPrefix
|
|---|
| 2829 | && self.left.operator === "~"
|
|---|
| 2830 | && self.right instanceof AST_UnaryPrefix
|
|---|
| 2831 | && self.right.operator === "~"
|
|---|
| 2832 | ) {
|
|---|
| 2833 | self = make_node(AST_Binary, self, {
|
|---|
| 2834 | operator: "^",
|
|---|
| 2835 | left: self.left.expression,
|
|---|
| 2836 | right: self.right.expression
|
|---|
| 2837 | });
|
|---|
| 2838 | }
|
|---|
| 2839 |
|
|---|
| 2840 |
|
|---|
| 2841 | // Shifts that do nothing
|
|---|
| 2842 | // {anything} >> 0 => {anything} | 0
|
|---|
| 2843 | // {anything} << 0 => {anything} | 0
|
|---|
| 2844 | if (
|
|---|
| 2845 | (self.operator === "<<" || self.operator === ">>")
|
|---|
| 2846 | && self.right instanceof AST_Number && self.right.value === 0
|
|---|
| 2847 | ) {
|
|---|
| 2848 | self.operator = "|";
|
|---|
| 2849 | }
|
|---|
| 2850 |
|
|---|
| 2851 | // Find useless to-bitwise conversions
|
|---|
| 2852 | // {32 bit integer} | 0 => {32 bit integer}
|
|---|
| 2853 | // {32 bit integer} ^ 0 => {32 bit integer}
|
|---|
| 2854 | const zero_side = self.right instanceof AST_Number && self.right.value === 0 ? self.right
|
|---|
| 2855 | : self.left instanceof AST_Number && self.left.value === 0 ? self.left
|
|---|
| 2856 | : null;
|
|---|
| 2857 | const non_zero_side = zero_side && (zero_side === self.right ? self.left : self.right);
|
|---|
| 2858 | if (
|
|---|
| 2859 | zero_side
|
|---|
| 2860 | && (self.operator === "|" || self.operator === "^")
|
|---|
| 2861 | && (non_zero_side.is_32_bit_integer(compressor) || compressor.in_32_bit_context(true))
|
|---|
| 2862 | ) {
|
|---|
| 2863 | return non_zero_side;
|
|---|
| 2864 | }
|
|---|
| 2865 |
|
|---|
| 2866 | // {anything} & 0 => 0
|
|---|
| 2867 | if (
|
|---|
| 2868 | zero_side
|
|---|
| 2869 | && self.operator === "&"
|
|---|
| 2870 | && !non_zero_side.has_side_effects(compressor)
|
|---|
| 2871 | && non_zero_side.is_32_bit_integer(compressor)
|
|---|
| 2872 | ) {
|
|---|
| 2873 | return zero_side;
|
|---|
| 2874 | }
|
|---|
| 2875 |
|
|---|
| 2876 | // ~0 is all ones, as well as -1.
|
|---|
| 2877 | // We can ellide some operations with it.
|
|---|
| 2878 | const is_full_mask = (node) =>
|
|---|
| 2879 | node instanceof AST_Number && node.value === -1
|
|---|
| 2880 | ||
|
|---|
| 2881 | node instanceof AST_UnaryPrefix
|
|---|
| 2882 | && node.operator === "-"
|
|---|
| 2883 | && node.expression instanceof AST_Number
|
|---|
| 2884 | && node.expression.value === 1;
|
|---|
| 2885 |
|
|---|
| 2886 | const full_mask = is_full_mask(self.right) ? self.right
|
|---|
| 2887 | : is_full_mask(self.left) ? self.left
|
|---|
| 2888 | : null;
|
|---|
| 2889 | const other_side = (full_mask === self.right ? self.left : self.right);
|
|---|
| 2890 |
|
|---|
| 2891 | // {32 bit integer} & -1 => {32 bit integer}
|
|---|
| 2892 | if (
|
|---|
| 2893 | full_mask
|
|---|
| 2894 | && self.operator === "&"
|
|---|
| 2895 | && (
|
|---|
| 2896 | other_side.is_32_bit_integer(compressor)
|
|---|
| 2897 | || compressor.in_32_bit_context(true)
|
|---|
| 2898 | )
|
|---|
| 2899 | ) {
|
|---|
| 2900 | return other_side;
|
|---|
| 2901 | }
|
|---|
| 2902 |
|
|---|
| 2903 | // {anything} ^ -1 => ~{anything}
|
|---|
| 2904 | if (
|
|---|
| 2905 | full_mask
|
|---|
| 2906 | && self.operator === "^"
|
|---|
| 2907 | && (
|
|---|
| 2908 | other_side.is_32_bit_integer(compressor)
|
|---|
| 2909 | || compressor.in_32_bit_context(true)
|
|---|
| 2910 | )
|
|---|
| 2911 | ) {
|
|---|
| 2912 | return other_side.bitwise_negate(compressor);
|
|---|
| 2913 | }
|
|---|
| 2914 | }
|
|---|
| 2915 | }
|
|---|
| 2916 | // x && (y && z) ==> x && y && z
|
|---|
| 2917 | // x || (y || z) ==> x || y || z
|
|---|
| 2918 | // x + ("y" + z) ==> x + "y" + z
|
|---|
| 2919 | // "x" + (y + "z")==> "x" + y + "z"
|
|---|
| 2920 | if (self.right instanceof AST_Binary
|
|---|
| 2921 | && self.right.operator == self.operator
|
|---|
| 2922 | && (lazy_op.has(self.operator)
|
|---|
| 2923 | || (self.operator == "+"
|
|---|
| 2924 | && (self.right.left.is_string(compressor)
|
|---|
| 2925 | || (self.left.is_string(compressor)
|
|---|
| 2926 | && self.right.right.is_string(compressor)))))
|
|---|
| 2927 | ) {
|
|---|
| 2928 | self.left = make_node(AST_Binary, self.left, {
|
|---|
| 2929 | operator : self.operator,
|
|---|
| 2930 | left : self.left.transform(compressor),
|
|---|
| 2931 | right : self.right.left.transform(compressor)
|
|---|
| 2932 | });
|
|---|
| 2933 | self.right = self.right.right.transform(compressor);
|
|---|
| 2934 | return self.transform(compressor);
|
|---|
| 2935 | }
|
|---|
| 2936 | var ev = self.evaluate(compressor);
|
|---|
| 2937 | if (ev !== self) {
|
|---|
| 2938 | ev = make_node_from_constant(ev, self).optimize(compressor);
|
|---|
| 2939 | return best_of(compressor, ev, self);
|
|---|
| 2940 | }
|
|---|
| 2941 | return self;
|
|---|
| 2942 | });
|
|---|
| 2943 |
|
|---|
| 2944 | def_optimize(AST_SymbolExport, function(self) {
|
|---|
| 2945 | return self;
|
|---|
| 2946 | });
|
|---|
| 2947 |
|
|---|
| 2948 | def_optimize(AST_SymbolRef, function(self, compressor) {
|
|---|
| 2949 | if (
|
|---|
| 2950 | !compressor.option("ie8")
|
|---|
| 2951 | && is_undeclared_ref(self)
|
|---|
| 2952 | && !compressor.find_parent(AST_With)
|
|---|
| 2953 | ) {
|
|---|
| 2954 | switch (self.name) {
|
|---|
| 2955 | case "undefined":
|
|---|
| 2956 | return make_node(AST_Undefined, self).optimize(compressor);
|
|---|
| 2957 | case "NaN":
|
|---|
| 2958 | return make_node(AST_NaN, self).optimize(compressor);
|
|---|
| 2959 | case "Infinity":
|
|---|
| 2960 | return make_node(AST_Infinity, self).optimize(compressor);
|
|---|
| 2961 | }
|
|---|
| 2962 | }
|
|---|
| 2963 |
|
|---|
| 2964 | if (compressor.option("reduce_vars") && !compressor.is_lhs()) {
|
|---|
| 2965 | return inline_into_symbolref(self, compressor);
|
|---|
| 2966 | } else {
|
|---|
| 2967 | return self;
|
|---|
| 2968 | }
|
|---|
| 2969 | });
|
|---|
| 2970 |
|
|---|
| 2971 | function is_atomic(lhs, self) {
|
|---|
| 2972 | return lhs instanceof AST_SymbolRef || lhs.TYPE === self.TYPE;
|
|---|
| 2973 | }
|
|---|
| 2974 |
|
|---|
| 2975 | /** Apply the `unsafe_undefined` option: find a variable called `undefined` and turn `self` into a reference to it. */
|
|---|
| 2976 | function unsafe_undefined_ref(self, compressor) {
|
|---|
| 2977 | if (compressor.option("unsafe_undefined")) {
|
|---|
| 2978 | var undef = find_variable(compressor, "undefined");
|
|---|
| 2979 | if (undef) {
|
|---|
| 2980 | var ref = make_node(AST_SymbolRef, self, {
|
|---|
| 2981 | name : "undefined",
|
|---|
| 2982 | scope : undef.scope,
|
|---|
| 2983 | thedef : undef
|
|---|
| 2984 | });
|
|---|
| 2985 | set_flag(ref, UNDEFINED);
|
|---|
| 2986 | return ref;
|
|---|
| 2987 | }
|
|---|
| 2988 | }
|
|---|
| 2989 | return null;
|
|---|
| 2990 | }
|
|---|
| 2991 |
|
|---|
| 2992 | def_optimize(AST_Undefined, function(self, compressor) {
|
|---|
| 2993 | var symbolref = unsafe_undefined_ref(self, compressor);
|
|---|
| 2994 | if (symbolref) return symbolref;
|
|---|
| 2995 | var lhs = compressor.is_lhs();
|
|---|
| 2996 | if (lhs && is_atomic(lhs, self)) return self;
|
|---|
| 2997 | return make_void_0(self);
|
|---|
| 2998 | });
|
|---|
| 2999 |
|
|---|
| 3000 | def_optimize(AST_Infinity, function(self, compressor) {
|
|---|
| 3001 | var lhs = compressor.is_lhs();
|
|---|
| 3002 | if (lhs && is_atomic(lhs, self)) return self;
|
|---|
| 3003 | if (
|
|---|
| 3004 | compressor.option("keep_infinity")
|
|---|
| 3005 | && !(lhs && !is_atomic(lhs, self))
|
|---|
| 3006 | && !find_variable(compressor, "Infinity")
|
|---|
| 3007 | ) {
|
|---|
| 3008 | return self;
|
|---|
| 3009 | }
|
|---|
| 3010 | return make_node(AST_Binary, self, {
|
|---|
| 3011 | operator: "/",
|
|---|
| 3012 | left: make_node(AST_Number, self, {
|
|---|
| 3013 | value: 1
|
|---|
| 3014 | }),
|
|---|
| 3015 | right: make_node(AST_Number, self, {
|
|---|
| 3016 | value: 0
|
|---|
| 3017 | })
|
|---|
| 3018 | });
|
|---|
| 3019 | });
|
|---|
| 3020 |
|
|---|
| 3021 | def_optimize(AST_NaN, function(self, compressor) {
|
|---|
| 3022 | var lhs = compressor.is_lhs();
|
|---|
| 3023 | if (lhs && !is_atomic(lhs, self)
|
|---|
| 3024 | || find_variable(compressor, "NaN")) {
|
|---|
| 3025 | return make_node(AST_Binary, self, {
|
|---|
| 3026 | operator: "/",
|
|---|
| 3027 | left: make_node(AST_Number, self, {
|
|---|
| 3028 | value: 0
|
|---|
| 3029 | }),
|
|---|
| 3030 | right: make_node(AST_Number, self, {
|
|---|
| 3031 | value: 0
|
|---|
| 3032 | })
|
|---|
| 3033 | });
|
|---|
| 3034 | }
|
|---|
| 3035 | return self;
|
|---|
| 3036 | });
|
|---|
| 3037 |
|
|---|
| 3038 | const ASSIGN_OPS = makePredicate("+ - / * % >> << >>> | ^ &");
|
|---|
| 3039 | const ASSIGN_OPS_COMMUTATIVE = makePredicate("* | ^ &");
|
|---|
| 3040 | def_optimize(AST_Assign, function(self, compressor) {
|
|---|
| 3041 | if (self.logical) {
|
|---|
| 3042 | return self.lift_sequences(compressor);
|
|---|
| 3043 | }
|
|---|
| 3044 |
|
|---|
| 3045 | var def;
|
|---|
| 3046 | // x = x ---> x
|
|---|
| 3047 | if (
|
|---|
| 3048 | self.operator === "="
|
|---|
| 3049 | && self.left instanceof AST_SymbolRef
|
|---|
| 3050 | && self.left.name !== "arguments"
|
|---|
| 3051 | && !(def = self.left.definition()).undeclared
|
|---|
| 3052 | && self.right.equivalent_to(self.left)
|
|---|
| 3053 | ) {
|
|---|
| 3054 | return self.right;
|
|---|
| 3055 | }
|
|---|
| 3056 |
|
|---|
| 3057 | if (compressor.option("dead_code")
|
|---|
| 3058 | && self.left instanceof AST_SymbolRef
|
|---|
| 3059 | && (def = self.left.definition()).scope === compressor.find_parent(AST_Lambda)) {
|
|---|
| 3060 | var level = 0, node, parent = self;
|
|---|
| 3061 | do {
|
|---|
| 3062 | node = parent;
|
|---|
| 3063 | parent = compressor.parent(level++);
|
|---|
| 3064 | if (parent instanceof AST_Exit) {
|
|---|
| 3065 | if (in_try(level, parent)) break;
|
|---|
| 3066 | if (is_reachable(def.scope, [ def ])) break;
|
|---|
| 3067 | if (self.operator == "=") return self.right;
|
|---|
| 3068 | def.fixed = false;
|
|---|
| 3069 | return make_node(AST_Binary, self, {
|
|---|
| 3070 | operator: self.operator.slice(0, -1),
|
|---|
| 3071 | left: self.left,
|
|---|
| 3072 | right: self.right
|
|---|
| 3073 | }).optimize(compressor);
|
|---|
| 3074 | }
|
|---|
| 3075 | } while (parent instanceof AST_Binary && parent.right === node
|
|---|
| 3076 | || parent instanceof AST_Sequence && parent.tail_node() === node);
|
|---|
| 3077 | }
|
|---|
| 3078 | self = self.lift_sequences(compressor);
|
|---|
| 3079 |
|
|---|
| 3080 | if (self.operator == "=" && self.left instanceof AST_SymbolRef && self.right instanceof AST_Binary) {
|
|---|
| 3081 | // x = expr1 OP expr2
|
|---|
| 3082 | if (self.right.left instanceof AST_SymbolRef
|
|---|
| 3083 | && self.right.left.name == self.left.name
|
|---|
| 3084 | && ASSIGN_OPS.has(self.right.operator)) {
|
|---|
| 3085 | // x = x - 2 ---> x -= 2
|
|---|
| 3086 | self.operator = self.right.operator + "=";
|
|---|
| 3087 | self.right = self.right.right;
|
|---|
| 3088 | } else if (self.right.right instanceof AST_SymbolRef
|
|---|
| 3089 | && self.right.right.name == self.left.name
|
|---|
| 3090 | && ASSIGN_OPS_COMMUTATIVE.has(self.right.operator)
|
|---|
| 3091 | && !self.right.left.has_side_effects(compressor)) {
|
|---|
| 3092 | // x = 2 & x ---> x &= 2
|
|---|
| 3093 | self.operator = self.right.operator + "=";
|
|---|
| 3094 | self.right = self.right.left;
|
|---|
| 3095 | }
|
|---|
| 3096 | }
|
|---|
| 3097 | return self;
|
|---|
| 3098 |
|
|---|
| 3099 | function in_try(level, node) {
|
|---|
| 3100 | function may_assignment_throw() {
|
|---|
| 3101 | const right = self.right;
|
|---|
| 3102 | self.right = make_node(AST_Null, right);
|
|---|
| 3103 | const may_throw = node.may_throw(compressor);
|
|---|
| 3104 | self.right = right;
|
|---|
| 3105 |
|
|---|
| 3106 | return may_throw;
|
|---|
| 3107 | }
|
|---|
| 3108 |
|
|---|
| 3109 | var stop_at = self.left.definition().scope.get_defun_scope();
|
|---|
| 3110 | var parent;
|
|---|
| 3111 | while ((parent = compressor.parent(level++)) !== stop_at) {
|
|---|
| 3112 | if (parent instanceof AST_Try) {
|
|---|
| 3113 | if (parent.bfinally) return true;
|
|---|
| 3114 | if (parent.bcatch && may_assignment_throw()) return true;
|
|---|
| 3115 | }
|
|---|
| 3116 | }
|
|---|
| 3117 | }
|
|---|
| 3118 | });
|
|---|
| 3119 |
|
|---|
| 3120 | def_optimize(AST_DefaultAssign, function(self, compressor) {
|
|---|
| 3121 | if (!compressor.option("evaluate")) {
|
|---|
| 3122 | return self;
|
|---|
| 3123 | }
|
|---|
| 3124 | var evaluateRight = self.right.evaluate(compressor);
|
|---|
| 3125 |
|
|---|
| 3126 | // `[x = undefined] = foo` ---> `[x] = foo`
|
|---|
| 3127 | // `(arg = undefined) => ...` ---> `(arg) => ...` (unless `keep_fargs`)
|
|---|
| 3128 | // `((arg = undefined) => ...)()` ---> `((arg) => ...)()`
|
|---|
| 3129 | let lambda, iife;
|
|---|
| 3130 | if (evaluateRight === undefined) {
|
|---|
| 3131 | if (
|
|---|
| 3132 | (lambda = compressor.parent()) instanceof AST_Lambda
|
|---|
| 3133 | ? (
|
|---|
| 3134 | compressor.option("keep_fargs") === false
|
|---|
| 3135 | || (iife = compressor.parent(1)).TYPE === "Call"
|
|---|
| 3136 | && iife.expression === lambda
|
|---|
| 3137 | )
|
|---|
| 3138 | : true
|
|---|
| 3139 | ) {
|
|---|
| 3140 | self = self.left;
|
|---|
| 3141 | }
|
|---|
| 3142 | } else if (evaluateRight !== self.right) {
|
|---|
| 3143 | evaluateRight = make_node_from_constant(evaluateRight, self.right);
|
|---|
| 3144 | self.right = best_of_expression(evaluateRight, self.right);
|
|---|
| 3145 | }
|
|---|
| 3146 |
|
|---|
| 3147 | return self;
|
|---|
| 3148 | });
|
|---|
| 3149 |
|
|---|
| 3150 | function is_nullish_check(check, check_subject, compressor) {
|
|---|
| 3151 | if (check_subject.may_throw(compressor)) return false;
|
|---|
| 3152 |
|
|---|
| 3153 | let nullish_side;
|
|---|
| 3154 |
|
|---|
| 3155 | // foo == null
|
|---|
| 3156 | if (
|
|---|
| 3157 | check instanceof AST_Binary
|
|---|
| 3158 | && check.operator === "=="
|
|---|
| 3159 | // which side is nullish?
|
|---|
| 3160 | && (
|
|---|
| 3161 | (nullish_side = is_nullish(check.left, compressor) && check.left)
|
|---|
| 3162 | || (nullish_side = is_nullish(check.right, compressor) && check.right)
|
|---|
| 3163 | )
|
|---|
| 3164 | // is the other side the same as the check_subject
|
|---|
| 3165 | && (
|
|---|
| 3166 | nullish_side === check.left
|
|---|
| 3167 | ? check.right
|
|---|
| 3168 | : check.left
|
|---|
| 3169 | ).equivalent_to(check_subject)
|
|---|
| 3170 | ) {
|
|---|
| 3171 | return true;
|
|---|
| 3172 | }
|
|---|
| 3173 |
|
|---|
| 3174 | // foo === null || foo === undefined
|
|---|
| 3175 | if (check instanceof AST_Binary && check.operator === "||") {
|
|---|
| 3176 | let null_cmp;
|
|---|
| 3177 | let undefined_cmp;
|
|---|
| 3178 |
|
|---|
| 3179 | const find_comparison = cmp => {
|
|---|
| 3180 | if (!(
|
|---|
| 3181 | cmp instanceof AST_Binary
|
|---|
| 3182 | && (cmp.operator === "===" || cmp.operator === "==")
|
|---|
| 3183 | )) {
|
|---|
| 3184 | return false;
|
|---|
| 3185 | }
|
|---|
| 3186 |
|
|---|
| 3187 | let found = 0;
|
|---|
| 3188 | let defined_side;
|
|---|
| 3189 |
|
|---|
| 3190 | if (cmp.left instanceof AST_Null) {
|
|---|
| 3191 | found++;
|
|---|
| 3192 | null_cmp = cmp;
|
|---|
| 3193 | defined_side = cmp.right;
|
|---|
| 3194 | }
|
|---|
| 3195 | if (cmp.right instanceof AST_Null) {
|
|---|
| 3196 | found++;
|
|---|
| 3197 | null_cmp = cmp;
|
|---|
| 3198 | defined_side = cmp.left;
|
|---|
| 3199 | }
|
|---|
| 3200 | if (is_undefined(cmp.left, compressor)) {
|
|---|
| 3201 | found++;
|
|---|
| 3202 | undefined_cmp = cmp;
|
|---|
| 3203 | defined_side = cmp.right;
|
|---|
| 3204 | }
|
|---|
| 3205 | if (is_undefined(cmp.right, compressor)) {
|
|---|
| 3206 | found++;
|
|---|
| 3207 | undefined_cmp = cmp;
|
|---|
| 3208 | defined_side = cmp.left;
|
|---|
| 3209 | }
|
|---|
| 3210 |
|
|---|
| 3211 | if (found !== 1) {
|
|---|
| 3212 | return false;
|
|---|
| 3213 | }
|
|---|
| 3214 |
|
|---|
| 3215 | if (!defined_side.equivalent_to(check_subject)) {
|
|---|
| 3216 | return false;
|
|---|
| 3217 | }
|
|---|
| 3218 |
|
|---|
| 3219 | return true;
|
|---|
| 3220 | };
|
|---|
| 3221 |
|
|---|
| 3222 | if (!find_comparison(check.left)) return false;
|
|---|
| 3223 | if (!find_comparison(check.right)) return false;
|
|---|
| 3224 |
|
|---|
| 3225 | if (null_cmp && undefined_cmp && null_cmp !== undefined_cmp) {
|
|---|
| 3226 | return true;
|
|---|
| 3227 | }
|
|---|
| 3228 | }
|
|---|
| 3229 |
|
|---|
| 3230 | return false;
|
|---|
| 3231 | }
|
|---|
| 3232 |
|
|---|
| 3233 | def_optimize(AST_Conditional, function(self, compressor) {
|
|---|
| 3234 | if (!compressor.option("conditionals")) return self;
|
|---|
| 3235 | // This looks like lift_sequences(), should probably be under "sequences"
|
|---|
| 3236 | if (self.condition instanceof AST_Sequence) {
|
|---|
| 3237 | var expressions = self.condition.expressions.slice();
|
|---|
| 3238 | self.condition = expressions.pop();
|
|---|
| 3239 | expressions.push(self);
|
|---|
| 3240 | return make_sequence(self, expressions);
|
|---|
| 3241 | }
|
|---|
| 3242 | var cond = self.condition.evaluate(compressor);
|
|---|
| 3243 | if (cond !== self.condition) {
|
|---|
| 3244 | if (cond) {
|
|---|
| 3245 | return maintain_this_binding(compressor.parent(), compressor.self(), self.consequent);
|
|---|
| 3246 | } else {
|
|---|
| 3247 | return maintain_this_binding(compressor.parent(), compressor.self(), self.alternative);
|
|---|
| 3248 | }
|
|---|
| 3249 | }
|
|---|
| 3250 | var negated = cond.negate(compressor, first_in_statement(compressor));
|
|---|
| 3251 | if (best_of(compressor, cond, negated) === negated) {
|
|---|
| 3252 | self = make_node(AST_Conditional, self, {
|
|---|
| 3253 | condition: negated,
|
|---|
| 3254 | consequent: self.alternative,
|
|---|
| 3255 | alternative: self.consequent
|
|---|
| 3256 | });
|
|---|
| 3257 | }
|
|---|
| 3258 | var condition = self.condition;
|
|---|
| 3259 | var consequent = self.consequent;
|
|---|
| 3260 | var alternative = self.alternative;
|
|---|
| 3261 | // x?x:y --> x||y
|
|---|
| 3262 | if (condition instanceof AST_SymbolRef
|
|---|
| 3263 | && consequent instanceof AST_SymbolRef
|
|---|
| 3264 | && condition.definition() === consequent.definition()) {
|
|---|
| 3265 | return make_node(AST_Binary, self, {
|
|---|
| 3266 | operator: "||",
|
|---|
| 3267 | left: condition,
|
|---|
| 3268 | right: alternative
|
|---|
| 3269 | });
|
|---|
| 3270 | }
|
|---|
| 3271 | // if (foo) exp = something; else exp = something_else;
|
|---|
| 3272 | // |
|
|---|
| 3273 | // v
|
|---|
| 3274 | // exp = foo ? something : something_else;
|
|---|
| 3275 | if (
|
|---|
| 3276 | consequent instanceof AST_Assign
|
|---|
| 3277 | && alternative instanceof AST_Assign
|
|---|
| 3278 | && consequent.operator === alternative.operator
|
|---|
| 3279 | && consequent.logical === alternative.logical
|
|---|
| 3280 | && consequent.left.equivalent_to(alternative.left)
|
|---|
| 3281 | && (!self.condition.has_side_effects(compressor)
|
|---|
| 3282 | || consequent.operator == "="
|
|---|
| 3283 | && !consequent.left.has_side_effects(compressor))
|
|---|
| 3284 | ) {
|
|---|
| 3285 | return make_node(AST_Assign, self, {
|
|---|
| 3286 | operator: consequent.operator,
|
|---|
| 3287 | left: consequent.left,
|
|---|
| 3288 | logical: consequent.logical,
|
|---|
| 3289 | right: make_node(AST_Conditional, self, {
|
|---|
| 3290 | condition: self.condition,
|
|---|
| 3291 | consequent: consequent.right,
|
|---|
| 3292 | alternative: alternative.right
|
|---|
| 3293 | })
|
|---|
| 3294 | });
|
|---|
| 3295 | }
|
|---|
| 3296 | // x ? y(a) : y(b) --> y(x ? a : b)
|
|---|
| 3297 | var arg_index;
|
|---|
| 3298 | if (consequent instanceof AST_Call
|
|---|
| 3299 | && alternative.TYPE === consequent.TYPE
|
|---|
| 3300 | && consequent.args.length > 0
|
|---|
| 3301 | && consequent.args.length == alternative.args.length
|
|---|
| 3302 | && consequent.expression.equivalent_to(alternative.expression)
|
|---|
| 3303 | && !self.condition.has_side_effects(compressor)
|
|---|
| 3304 | && !consequent.expression.has_side_effects(compressor)
|
|---|
| 3305 | && typeof (arg_index = single_arg_diff()) == "number") {
|
|---|
| 3306 | var node = consequent.clone();
|
|---|
| 3307 | node.args[arg_index] = make_node(AST_Conditional, self, {
|
|---|
| 3308 | condition: self.condition,
|
|---|
| 3309 | consequent: consequent.args[arg_index],
|
|---|
| 3310 | alternative: alternative.args[arg_index]
|
|---|
| 3311 | });
|
|---|
| 3312 | return node;
|
|---|
| 3313 | }
|
|---|
| 3314 | // a ? b : c ? b : d --> (a || c) ? b : d
|
|---|
| 3315 | if (alternative instanceof AST_Conditional
|
|---|
| 3316 | && consequent.equivalent_to(alternative.consequent)) {
|
|---|
| 3317 | return make_node(AST_Conditional, self, {
|
|---|
| 3318 | condition: make_node(AST_Binary, self, {
|
|---|
| 3319 | operator: "||",
|
|---|
| 3320 | left: condition,
|
|---|
| 3321 | right: alternative.condition
|
|---|
| 3322 | }),
|
|---|
| 3323 | consequent: consequent,
|
|---|
| 3324 | alternative: alternative.alternative
|
|---|
| 3325 | }).optimize(compressor);
|
|---|
| 3326 | }
|
|---|
| 3327 |
|
|---|
| 3328 | // a == null ? b : a -> a ?? b
|
|---|
| 3329 | if (
|
|---|
| 3330 | compressor.option("ecma") >= 2020 &&
|
|---|
| 3331 | is_nullish_check(condition, alternative, compressor)
|
|---|
| 3332 | ) {
|
|---|
| 3333 | return make_node(AST_Binary, self, {
|
|---|
| 3334 | operator: "??",
|
|---|
| 3335 | left: alternative,
|
|---|
| 3336 | right: consequent
|
|---|
| 3337 | }).optimize(compressor);
|
|---|
| 3338 | }
|
|---|
| 3339 |
|
|---|
| 3340 | // a ? b : (c, b) --> (a || c), b
|
|---|
| 3341 | if (alternative instanceof AST_Sequence
|
|---|
| 3342 | && consequent.equivalent_to(alternative.expressions[alternative.expressions.length - 1])) {
|
|---|
| 3343 | return make_sequence(self, [
|
|---|
| 3344 | make_node(AST_Binary, self, {
|
|---|
| 3345 | operator: "||",
|
|---|
| 3346 | left: condition,
|
|---|
| 3347 | right: make_sequence(self, alternative.expressions.slice(0, -1))
|
|---|
| 3348 | }),
|
|---|
| 3349 | consequent
|
|---|
| 3350 | ]).optimize(compressor);
|
|---|
| 3351 | }
|
|---|
| 3352 | // a ? b : (c && b) --> (a || c) && b
|
|---|
| 3353 | if (alternative instanceof AST_Binary
|
|---|
| 3354 | && alternative.operator == "&&"
|
|---|
| 3355 | && consequent.equivalent_to(alternative.right)) {
|
|---|
| 3356 | return make_node(AST_Binary, self, {
|
|---|
| 3357 | operator: "&&",
|
|---|
| 3358 | left: make_node(AST_Binary, self, {
|
|---|
| 3359 | operator: "||",
|
|---|
| 3360 | left: condition,
|
|---|
| 3361 | right: alternative.left
|
|---|
| 3362 | }),
|
|---|
| 3363 | right: consequent
|
|---|
| 3364 | }).optimize(compressor);
|
|---|
| 3365 | }
|
|---|
| 3366 | // x?y?z:a:a --> x&&y?z:a
|
|---|
| 3367 | if (consequent instanceof AST_Conditional
|
|---|
| 3368 | && consequent.alternative.equivalent_to(alternative)) {
|
|---|
| 3369 | return make_node(AST_Conditional, self, {
|
|---|
| 3370 | condition: make_node(AST_Binary, self, {
|
|---|
| 3371 | left: self.condition,
|
|---|
| 3372 | operator: "&&",
|
|---|
| 3373 | right: consequent.condition
|
|---|
| 3374 | }),
|
|---|
| 3375 | consequent: consequent.consequent,
|
|---|
| 3376 | alternative: alternative
|
|---|
| 3377 | });
|
|---|
| 3378 | }
|
|---|
| 3379 | // x ? y : y --> x, y
|
|---|
| 3380 | if (consequent.equivalent_to(alternative)) {
|
|---|
| 3381 | return make_sequence(self, [
|
|---|
| 3382 | self.condition,
|
|---|
| 3383 | consequent
|
|---|
| 3384 | ]).optimize(compressor);
|
|---|
| 3385 | }
|
|---|
| 3386 | // x ? y || z : z --> x && y || z
|
|---|
| 3387 | if (consequent instanceof AST_Binary
|
|---|
| 3388 | && consequent.operator == "||"
|
|---|
| 3389 | && consequent.right.equivalent_to(alternative)) {
|
|---|
| 3390 | return make_node(AST_Binary, self, {
|
|---|
| 3391 | operator: "||",
|
|---|
| 3392 | left: make_node(AST_Binary, self, {
|
|---|
| 3393 | operator: "&&",
|
|---|
| 3394 | left: self.condition,
|
|---|
| 3395 | right: consequent.left
|
|---|
| 3396 | }),
|
|---|
| 3397 | right: alternative
|
|---|
| 3398 | }).optimize(compressor);
|
|---|
| 3399 | }
|
|---|
| 3400 |
|
|---|
| 3401 | const in_bool = compressor.in_boolean_context();
|
|---|
| 3402 | if (is_true(self.consequent)) {
|
|---|
| 3403 | if (is_false(self.alternative)) {
|
|---|
| 3404 | // c ? true : false ---> !!c
|
|---|
| 3405 | return booleanize(self.condition);
|
|---|
| 3406 | }
|
|---|
| 3407 | // c ? true : x ---> !!c || x
|
|---|
| 3408 | return make_node(AST_Binary, self, {
|
|---|
| 3409 | operator: "||",
|
|---|
| 3410 | left: booleanize(self.condition),
|
|---|
| 3411 | right: self.alternative
|
|---|
| 3412 | });
|
|---|
| 3413 | }
|
|---|
| 3414 | if (is_false(self.consequent)) {
|
|---|
| 3415 | if (is_true(self.alternative)) {
|
|---|
| 3416 | // c ? false : true ---> !c
|
|---|
| 3417 | return booleanize(self.condition.negate(compressor));
|
|---|
| 3418 | }
|
|---|
| 3419 | // c ? false : x ---> !c && x
|
|---|
| 3420 | return make_node(AST_Binary, self, {
|
|---|
| 3421 | operator: "&&",
|
|---|
| 3422 | left: booleanize(self.condition.negate(compressor)),
|
|---|
| 3423 | right: self.alternative
|
|---|
| 3424 | });
|
|---|
| 3425 | }
|
|---|
| 3426 | if (is_true(self.alternative)) {
|
|---|
| 3427 | // c ? x : true ---> !c || x
|
|---|
| 3428 | return make_node(AST_Binary, self, {
|
|---|
| 3429 | operator: "||",
|
|---|
| 3430 | left: booleanize(self.condition.negate(compressor)),
|
|---|
| 3431 | right: self.consequent
|
|---|
| 3432 | });
|
|---|
| 3433 | }
|
|---|
| 3434 | if (is_false(self.alternative)) {
|
|---|
| 3435 | // c ? x : false ---> !!c && x
|
|---|
| 3436 | return make_node(AST_Binary, self, {
|
|---|
| 3437 | operator: "&&",
|
|---|
| 3438 | left: booleanize(self.condition),
|
|---|
| 3439 | right: self.consequent
|
|---|
| 3440 | });
|
|---|
| 3441 | }
|
|---|
| 3442 |
|
|---|
| 3443 | return self;
|
|---|
| 3444 |
|
|---|
| 3445 | function booleanize(node) {
|
|---|
| 3446 | if (node.is_boolean()) return node;
|
|---|
| 3447 | // !!expression
|
|---|
| 3448 | return make_node(AST_UnaryPrefix, node, {
|
|---|
| 3449 | operator: "!",
|
|---|
| 3450 | expression: node.negate(compressor)
|
|---|
| 3451 | });
|
|---|
| 3452 | }
|
|---|
| 3453 |
|
|---|
| 3454 | // AST_True or !0
|
|---|
| 3455 | function is_true(node) {
|
|---|
| 3456 | return node instanceof AST_True
|
|---|
| 3457 | || in_bool
|
|---|
| 3458 | && node instanceof AST_Constant
|
|---|
| 3459 | && node.getValue()
|
|---|
| 3460 | || (node instanceof AST_UnaryPrefix
|
|---|
| 3461 | && node.operator == "!"
|
|---|
| 3462 | && node.expression instanceof AST_Constant
|
|---|
| 3463 | && !node.expression.getValue());
|
|---|
| 3464 | }
|
|---|
| 3465 | // AST_False or !1
|
|---|
| 3466 | function is_false(node) {
|
|---|
| 3467 | return node instanceof AST_False
|
|---|
| 3468 | || in_bool
|
|---|
| 3469 | && node instanceof AST_Constant
|
|---|
| 3470 | && !node.getValue()
|
|---|
| 3471 | || (node instanceof AST_UnaryPrefix
|
|---|
| 3472 | && node.operator == "!"
|
|---|
| 3473 | && node.expression instanceof AST_Constant
|
|---|
| 3474 | && node.expression.getValue());
|
|---|
| 3475 | }
|
|---|
| 3476 |
|
|---|
| 3477 | function single_arg_diff() {
|
|---|
| 3478 | var a = consequent.args;
|
|---|
| 3479 | var b = alternative.args;
|
|---|
| 3480 | for (var i = 0, len = a.length; i < len; i++) {
|
|---|
| 3481 | if (a[i] instanceof AST_Expansion) return;
|
|---|
| 3482 | if (!a[i].equivalent_to(b[i])) {
|
|---|
| 3483 | if (b[i] instanceof AST_Expansion) return;
|
|---|
| 3484 | for (var j = i + 1; j < len; j++) {
|
|---|
| 3485 | if (a[j] instanceof AST_Expansion) return;
|
|---|
| 3486 | if (!a[j].equivalent_to(b[j])) return;
|
|---|
| 3487 | }
|
|---|
| 3488 | return i;
|
|---|
| 3489 | }
|
|---|
| 3490 | }
|
|---|
| 3491 | }
|
|---|
| 3492 | });
|
|---|
| 3493 |
|
|---|
| 3494 | def_optimize(AST_Boolean, function(self, compressor) {
|
|---|
| 3495 | if (compressor.in_boolean_context()) return make_node(AST_Number, self, {
|
|---|
| 3496 | value: +self.value
|
|---|
| 3497 | });
|
|---|
| 3498 | var p = compressor.parent();
|
|---|
| 3499 | if (compressor.option("booleans_as_integers")) {
|
|---|
| 3500 | if (p instanceof AST_Binary && (p.operator == "===" || p.operator == "!==")) {
|
|---|
| 3501 | p.operator = p.operator.replace(/=$/, "");
|
|---|
| 3502 | }
|
|---|
| 3503 | return make_node(AST_Number, self, {
|
|---|
| 3504 | value: +self.value
|
|---|
| 3505 | });
|
|---|
| 3506 | }
|
|---|
| 3507 | if (compressor.option("booleans")) {
|
|---|
| 3508 | if (p instanceof AST_Binary && (p.operator == "=="
|
|---|
| 3509 | || p.operator == "!=")) {
|
|---|
| 3510 | return make_node(AST_Number, self, {
|
|---|
| 3511 | value: +self.value
|
|---|
| 3512 | });
|
|---|
| 3513 | }
|
|---|
| 3514 | return make_node(AST_UnaryPrefix, self, {
|
|---|
| 3515 | operator: "!",
|
|---|
| 3516 | expression: make_node(AST_Number, self, {
|
|---|
| 3517 | value: 1 - self.value
|
|---|
| 3518 | })
|
|---|
| 3519 | });
|
|---|
| 3520 | }
|
|---|
| 3521 | return self;
|
|---|
| 3522 | });
|
|---|
| 3523 |
|
|---|
| 3524 | function safe_to_flatten(value, compressor) {
|
|---|
| 3525 | if (value instanceof AST_SymbolRef) {
|
|---|
| 3526 | value = value.fixed_value();
|
|---|
| 3527 | }
|
|---|
| 3528 | if (!value) return false;
|
|---|
| 3529 | if (!(value instanceof AST_Lambda || value instanceof AST_Class)) return true;
|
|---|
| 3530 | if (!(value instanceof AST_Lambda && value.contains_this())) return true;
|
|---|
| 3531 | return compressor.parent() instanceof AST_New;
|
|---|
| 3532 | }
|
|---|
| 3533 |
|
|---|
| 3534 | AST_PropAccess.DEFMETHOD("flatten_object", function(key, compressor) {
|
|---|
| 3535 | if (!compressor.option("properties")) return;
|
|---|
| 3536 | if (key === "__proto__") return;
|
|---|
| 3537 | if (this instanceof AST_DotHash) return;
|
|---|
| 3538 |
|
|---|
| 3539 | var arrows = compressor.option("unsafe_arrows") && compressor.option("ecma") >= 2015;
|
|---|
| 3540 | var expr = this.expression;
|
|---|
| 3541 | if (expr instanceof AST_Object) {
|
|---|
| 3542 | var props = expr.properties;
|
|---|
| 3543 |
|
|---|
| 3544 | for (var i = props.length; --i >= 0;) {
|
|---|
| 3545 | var prop = props[i];
|
|---|
| 3546 |
|
|---|
| 3547 | if ("" + (prop instanceof AST_ConciseMethod ? prop.key.name : prop.key) == key) {
|
|---|
| 3548 | const all_props_flattenable = props.every((p) =>
|
|---|
| 3549 | (p instanceof AST_ObjectKeyVal
|
|---|
| 3550 | || arrows && p instanceof AST_ConciseMethod && !p.value.is_generator
|
|---|
| 3551 | )
|
|---|
| 3552 | && !p.computed_key()
|
|---|
| 3553 | );
|
|---|
| 3554 |
|
|---|
| 3555 | if (!all_props_flattenable) return;
|
|---|
| 3556 | if (!safe_to_flatten(prop.value, compressor)) return;
|
|---|
| 3557 |
|
|---|
| 3558 | return make_node(AST_Sub, this, {
|
|---|
| 3559 | expression: make_node(AST_Array, expr, {
|
|---|
| 3560 | elements: props.map(function(prop) {
|
|---|
| 3561 | var v = prop.value;
|
|---|
| 3562 | if (v instanceof AST_Accessor) {
|
|---|
| 3563 | v = make_node(AST_Function, v, v);
|
|---|
| 3564 | }
|
|---|
| 3565 |
|
|---|
| 3566 | var k = prop.key;
|
|---|
| 3567 | if (k instanceof AST_Node && !(k instanceof AST_SymbolMethod)) {
|
|---|
| 3568 | return make_sequence(prop, [ k, v ]);
|
|---|
| 3569 | }
|
|---|
| 3570 |
|
|---|
| 3571 | return v;
|
|---|
| 3572 | })
|
|---|
| 3573 | }),
|
|---|
| 3574 | property: make_node(AST_Number, this, {
|
|---|
| 3575 | value: i
|
|---|
| 3576 | })
|
|---|
| 3577 | });
|
|---|
| 3578 | }
|
|---|
| 3579 | }
|
|---|
| 3580 | }
|
|---|
| 3581 | });
|
|---|
| 3582 |
|
|---|
| 3583 | def_optimize(AST_Sub, function(self, compressor) {
|
|---|
| 3584 | var expr = self.expression;
|
|---|
| 3585 | var prop = self.property;
|
|---|
| 3586 | if (compressor.option("properties")) {
|
|---|
| 3587 | var key = prop.evaluate(compressor);
|
|---|
| 3588 | if (key !== prop) {
|
|---|
| 3589 | if (typeof key == "string") {
|
|---|
| 3590 | if (key == "undefined") {
|
|---|
| 3591 | key = undefined;
|
|---|
| 3592 | } else {
|
|---|
| 3593 | var value = parseFloat(key);
|
|---|
| 3594 | if (value.toString() == key) {
|
|---|
| 3595 | key = value;
|
|---|
| 3596 | }
|
|---|
| 3597 | }
|
|---|
| 3598 | }
|
|---|
| 3599 | prop = self.property = best_of_expression(
|
|---|
| 3600 | prop,
|
|---|
| 3601 | make_node_from_constant(key, prop).transform(compressor)
|
|---|
| 3602 | );
|
|---|
| 3603 | var property = "" + key;
|
|---|
| 3604 | if (is_basic_identifier_string(property)
|
|---|
| 3605 | && property.length <= prop.size() + 1) {
|
|---|
| 3606 | return make_node(AST_Dot, self, {
|
|---|
| 3607 | expression: expr,
|
|---|
| 3608 | optional: self.optional,
|
|---|
| 3609 | property: property,
|
|---|
| 3610 | quote: prop.quote,
|
|---|
| 3611 | }).optimize(compressor);
|
|---|
| 3612 | }
|
|---|
| 3613 | }
|
|---|
| 3614 | }
|
|---|
| 3615 | var fn;
|
|---|
| 3616 | OPT_ARGUMENTS: if (compressor.option("arguments")
|
|---|
| 3617 | && expr instanceof AST_SymbolRef
|
|---|
| 3618 | && expr.name == "arguments"
|
|---|
| 3619 | && expr.definition().orig.length == 1
|
|---|
| 3620 | && (fn = expr.scope) instanceof AST_Lambda
|
|---|
| 3621 | && fn.uses_arguments
|
|---|
| 3622 | && !(fn instanceof AST_Arrow)
|
|---|
| 3623 | && prop instanceof AST_Number) {
|
|---|
| 3624 | var index = prop.getValue();
|
|---|
| 3625 | var params = new Set();
|
|---|
| 3626 | var argnames = fn.argnames;
|
|---|
| 3627 | for (var n = 0; n < argnames.length; n++) {
|
|---|
| 3628 | if (!(argnames[n] instanceof AST_SymbolFunarg)) {
|
|---|
| 3629 | break OPT_ARGUMENTS; // destructuring parameter - bail
|
|---|
| 3630 | }
|
|---|
| 3631 | var param = argnames[n].name;
|
|---|
| 3632 | if (params.has(param)) {
|
|---|
| 3633 | break OPT_ARGUMENTS; // duplicate parameter - bail
|
|---|
| 3634 | }
|
|---|
| 3635 | params.add(param);
|
|---|
| 3636 | }
|
|---|
| 3637 | var argname = fn.argnames[index];
|
|---|
| 3638 | if (argname && compressor.has_directive("use strict")) {
|
|---|
| 3639 | var def = argname.definition();
|
|---|
| 3640 | if (!compressor.option("reduce_vars") || def.assignments || def.orig.length > 1) {
|
|---|
| 3641 | argname = null;
|
|---|
| 3642 | }
|
|---|
| 3643 | } else if (!argname && !compressor.option("keep_fargs") && index < fn.argnames.length + 5) {
|
|---|
| 3644 | while (index >= fn.argnames.length) {
|
|---|
| 3645 | argname = fn.create_symbol(AST_SymbolFunarg, {
|
|---|
| 3646 | source: fn,
|
|---|
| 3647 | scope: fn,
|
|---|
| 3648 | tentative_name: "argument_" + fn.argnames.length,
|
|---|
| 3649 | });
|
|---|
| 3650 | fn.argnames.push(argname);
|
|---|
| 3651 | }
|
|---|
| 3652 | }
|
|---|
| 3653 | if (argname) {
|
|---|
| 3654 | var sym = make_node(AST_SymbolRef, self, argname);
|
|---|
| 3655 | sym.reference({});
|
|---|
| 3656 | clear_flag(argname, UNUSED);
|
|---|
| 3657 | return sym;
|
|---|
| 3658 | }
|
|---|
| 3659 | }
|
|---|
| 3660 | if (compressor.is_lhs()) return self;
|
|---|
| 3661 | if (key !== prop) {
|
|---|
| 3662 | var sub = self.flatten_object(property, compressor);
|
|---|
| 3663 | if (sub) {
|
|---|
| 3664 | expr = self.expression = sub.expression;
|
|---|
| 3665 | prop = self.property = sub.property;
|
|---|
| 3666 | }
|
|---|
| 3667 | }
|
|---|
| 3668 | if (compressor.option("properties") && compressor.option("side_effects")
|
|---|
| 3669 | && prop instanceof AST_Number && expr instanceof AST_Array) {
|
|---|
| 3670 | var index = prop.getValue();
|
|---|
| 3671 | var elements = expr.elements;
|
|---|
| 3672 | var retValue = elements[index];
|
|---|
| 3673 | FLATTEN: if (safe_to_flatten(retValue, compressor)) {
|
|---|
| 3674 | var flatten = true;
|
|---|
| 3675 | var values = [];
|
|---|
| 3676 | for (var i = elements.length; --i > index;) {
|
|---|
| 3677 | var value = elements[i].drop_side_effect_free(compressor);
|
|---|
| 3678 | if (value) {
|
|---|
| 3679 | values.unshift(value);
|
|---|
| 3680 | if (flatten && value.has_side_effects(compressor)) flatten = false;
|
|---|
| 3681 | }
|
|---|
| 3682 | }
|
|---|
| 3683 | if (retValue instanceof AST_Expansion) break FLATTEN;
|
|---|
| 3684 | retValue = retValue instanceof AST_Hole ? make_void_0(retValue) : retValue;
|
|---|
| 3685 | if (!flatten) values.unshift(retValue);
|
|---|
| 3686 | while (--i >= 0) {
|
|---|
| 3687 | var value = elements[i];
|
|---|
| 3688 | if (value instanceof AST_Expansion) break FLATTEN;
|
|---|
| 3689 | value = value.drop_side_effect_free(compressor);
|
|---|
| 3690 | if (value) values.unshift(value);
|
|---|
| 3691 | else index--;
|
|---|
| 3692 | }
|
|---|
| 3693 | if (flatten) {
|
|---|
| 3694 | values.push(retValue);
|
|---|
| 3695 | return make_sequence(self, values).optimize(compressor);
|
|---|
| 3696 | } else return make_node(AST_Sub, self, {
|
|---|
| 3697 | expression: make_node(AST_Array, expr, {
|
|---|
| 3698 | elements: values
|
|---|
| 3699 | }),
|
|---|
| 3700 | property: make_node(AST_Number, prop, {
|
|---|
| 3701 | value: index
|
|---|
| 3702 | })
|
|---|
| 3703 | });
|
|---|
| 3704 | }
|
|---|
| 3705 | }
|
|---|
| 3706 | var ev = self.evaluate(compressor);
|
|---|
| 3707 | if (ev !== self) {
|
|---|
| 3708 | ev = make_node_from_constant(ev, self).optimize(compressor);
|
|---|
| 3709 | return best_of(compressor, ev, self);
|
|---|
| 3710 | }
|
|---|
| 3711 | return self;
|
|---|
| 3712 | });
|
|---|
| 3713 |
|
|---|
| 3714 | def_optimize(AST_Chain, function (self, compressor) {
|
|---|
| 3715 | if (is_nullish(self.expression, compressor)) {
|
|---|
| 3716 | let parent = compressor.parent();
|
|---|
| 3717 | // It's valid to delete a nullish optional chain, but if we optimized
|
|---|
| 3718 | // this to `delete undefined` then it would appear to be a syntax error
|
|---|
| 3719 | // when we try to optimize the delete. Thankfully, `delete 0` is fine.
|
|---|
| 3720 | if (parent instanceof AST_UnaryPrefix && parent.operator === "delete") {
|
|---|
| 3721 | return make_node_from_constant(0, self);
|
|---|
| 3722 | }
|
|---|
| 3723 | return make_void_0(self).optimize(compressor);
|
|---|
| 3724 | }
|
|---|
| 3725 | if (
|
|---|
| 3726 | self.expression instanceof AST_PropAccess
|
|---|
| 3727 | || self.expression instanceof AST_Call
|
|---|
| 3728 | ) {
|
|---|
| 3729 | return self;
|
|---|
| 3730 | } else {
|
|---|
| 3731 | // Keep the AST valid, in case the child swapped itself
|
|---|
| 3732 | return self.expression;
|
|---|
| 3733 | }
|
|---|
| 3734 | });
|
|---|
| 3735 |
|
|---|
| 3736 | def_optimize(AST_Dot, function(self, compressor) {
|
|---|
| 3737 | const parent = compressor.parent();
|
|---|
| 3738 | if (compressor.is_lhs()) return self;
|
|---|
| 3739 | if (compressor.option("unsafe_proto")
|
|---|
| 3740 | && self.expression instanceof AST_Dot
|
|---|
| 3741 | && self.expression.property == "prototype") {
|
|---|
| 3742 | var exp = self.expression.expression;
|
|---|
| 3743 | if (is_undeclared_ref(exp)) switch (exp.name) {
|
|---|
| 3744 | case "Array":
|
|---|
| 3745 | self.expression = make_node(AST_Array, self.expression, {
|
|---|
| 3746 | elements: []
|
|---|
| 3747 | });
|
|---|
| 3748 | break;
|
|---|
| 3749 | case "Function":
|
|---|
| 3750 | self.expression = make_empty_function(self.expression);
|
|---|
| 3751 | break;
|
|---|
| 3752 | case "Number":
|
|---|
| 3753 | self.expression = make_node(AST_Number, self.expression, {
|
|---|
| 3754 | value: 0
|
|---|
| 3755 | });
|
|---|
| 3756 | break;
|
|---|
| 3757 | case "Object":
|
|---|
| 3758 | self.expression = make_node(AST_Object, self.expression, {
|
|---|
| 3759 | properties: []
|
|---|
| 3760 | });
|
|---|
| 3761 | break;
|
|---|
| 3762 | case "RegExp":
|
|---|
| 3763 | self.expression = make_node(AST_RegExp, self.expression, {
|
|---|
| 3764 | value: { source: "t", flags: "" }
|
|---|
| 3765 | });
|
|---|
| 3766 | break;
|
|---|
| 3767 | case "String":
|
|---|
| 3768 | self.expression = make_node(AST_String, self.expression, {
|
|---|
| 3769 | value: ""
|
|---|
| 3770 | });
|
|---|
| 3771 | break;
|
|---|
| 3772 | }
|
|---|
| 3773 | }
|
|---|
| 3774 | if (!(parent instanceof AST_Call) || !has_annotation(parent, _NOINLINE)) {
|
|---|
| 3775 | const sub = self.flatten_object(self.property, compressor);
|
|---|
| 3776 | if (sub) return sub.optimize(compressor);
|
|---|
| 3777 | }
|
|---|
| 3778 |
|
|---|
| 3779 | if (self.expression instanceof AST_PropAccess
|
|---|
| 3780 | && parent instanceof AST_PropAccess) {
|
|---|
| 3781 | return self;
|
|---|
| 3782 | }
|
|---|
| 3783 |
|
|---|
| 3784 | let ev = self.evaluate(compressor);
|
|---|
| 3785 | if (ev !== self) {
|
|---|
| 3786 | ev = make_node_from_constant(ev, self).optimize(compressor);
|
|---|
| 3787 | return best_of(compressor, ev, self);
|
|---|
| 3788 | }
|
|---|
| 3789 | return self;
|
|---|
| 3790 | });
|
|---|
| 3791 |
|
|---|
| 3792 | function literals_in_boolean_context(self, compressor) {
|
|---|
| 3793 | if (compressor.in_boolean_context()) {
|
|---|
| 3794 | return best_of(compressor, self, make_sequence(self, [
|
|---|
| 3795 | self,
|
|---|
| 3796 | make_node(AST_True, self)
|
|---|
| 3797 | ]).optimize(compressor));
|
|---|
| 3798 | }
|
|---|
| 3799 | return self;
|
|---|
| 3800 | }
|
|---|
| 3801 |
|
|---|
| 3802 | function inline_array_like_spread(elements) {
|
|---|
| 3803 | for (var i = 0; i < elements.length; i++) {
|
|---|
| 3804 | var el = elements[i];
|
|---|
| 3805 | if (el instanceof AST_Expansion) {
|
|---|
| 3806 | var expr = el.expression;
|
|---|
| 3807 | if (
|
|---|
| 3808 | expr instanceof AST_Array
|
|---|
| 3809 | && !expr.elements.some(elm => elm instanceof AST_Hole)
|
|---|
| 3810 | ) {
|
|---|
| 3811 | elements.splice(i, 1, ...expr.elements);
|
|---|
| 3812 | // Step back one, as the element at i is now new.
|
|---|
| 3813 | i--;
|
|---|
| 3814 | }
|
|---|
| 3815 | // In array-like spread, spreading a non-iterable value is TypeError.
|
|---|
| 3816 | // We therefore can’t optimize anything else, unlike with object spread.
|
|---|
| 3817 | }
|
|---|
| 3818 | }
|
|---|
| 3819 | }
|
|---|
| 3820 |
|
|---|
| 3821 | def_optimize(AST_Array, function(self, compressor) {
|
|---|
| 3822 | var optimized = literals_in_boolean_context(self, compressor);
|
|---|
| 3823 | if (optimized !== self) {
|
|---|
| 3824 | return optimized;
|
|---|
| 3825 | }
|
|---|
| 3826 | inline_array_like_spread(self.elements);
|
|---|
| 3827 | return self;
|
|---|
| 3828 | });
|
|---|
| 3829 |
|
|---|
| 3830 | function inline_object_prop_spread(props) {
|
|---|
| 3831 | for (var i = 0; i < props.length; i++) {
|
|---|
| 3832 | var prop = props[i];
|
|---|
| 3833 | if (prop instanceof AST_Expansion) {
|
|---|
| 3834 | const expr = prop.expression;
|
|---|
| 3835 | if (
|
|---|
| 3836 | expr instanceof AST_Object
|
|---|
| 3837 | && expr.properties.every(prop => prop instanceof AST_ObjectKeyVal)
|
|---|
| 3838 | ) {
|
|---|
| 3839 | props.splice(i, 1, ...expr.properties);
|
|---|
| 3840 | // Step back one, as the property at i is now new.
|
|---|
| 3841 | i--;
|
|---|
| 3842 | } else if ((
|
|---|
| 3843 | // `expr.is_constant()` returns `false` for `AST_RegExp`, so need both.
|
|---|
| 3844 | expr instanceof AST_Constant
|
|---|
| 3845 | || expr.is_constant()
|
|---|
| 3846 | ) && !(expr instanceof AST_String)) {
|
|---|
| 3847 | // Unlike array-like spread, in object spread, spreading a
|
|---|
| 3848 | // non-iterable value silently does nothing; it is thus safe
|
|---|
| 3849 | // to remove. AST_String is the only iterable constant.
|
|---|
| 3850 | props.splice(i, 1);
|
|---|
| 3851 | i--;
|
|---|
| 3852 | }
|
|---|
| 3853 | }
|
|---|
| 3854 | }
|
|---|
| 3855 | }
|
|---|
| 3856 |
|
|---|
| 3857 | def_optimize(AST_Object, function(self, compressor) {
|
|---|
| 3858 | var optimized = literals_in_boolean_context(self, compressor);
|
|---|
| 3859 | if (optimized !== self) {
|
|---|
| 3860 | return optimized;
|
|---|
| 3861 | }
|
|---|
| 3862 | inline_object_prop_spread(self.properties);
|
|---|
| 3863 | return self;
|
|---|
| 3864 | });
|
|---|
| 3865 |
|
|---|
| 3866 | def_optimize(AST_RegExp, literals_in_boolean_context);
|
|---|
| 3867 |
|
|---|
| 3868 | def_optimize(AST_Return, function(self, compressor) {
|
|---|
| 3869 | if (self.value && is_undefined(self.value, compressor)) {
|
|---|
| 3870 | self.value = null;
|
|---|
| 3871 | }
|
|---|
| 3872 | return self;
|
|---|
| 3873 | });
|
|---|
| 3874 |
|
|---|
| 3875 | def_optimize(AST_Arrow, opt_AST_Lambda);
|
|---|
| 3876 |
|
|---|
| 3877 | def_optimize(AST_Function, function(self, compressor) {
|
|---|
| 3878 | self = opt_AST_Lambda(self, compressor);
|
|---|
| 3879 | if (compressor.option("unsafe_arrows")
|
|---|
| 3880 | && compressor.option("ecma") >= 2015
|
|---|
| 3881 | && !self.name
|
|---|
| 3882 | && !self.is_generator
|
|---|
| 3883 | && !self.uses_arguments
|
|---|
| 3884 | && !self.pinned()) {
|
|---|
| 3885 | const uses_this = walk(self, node => {
|
|---|
| 3886 | if (node instanceof AST_This) return walk_abort;
|
|---|
| 3887 | });
|
|---|
| 3888 | if (!uses_this) return make_node(AST_Arrow, self, self).optimize(compressor);
|
|---|
| 3889 | }
|
|---|
| 3890 | return self;
|
|---|
| 3891 | });
|
|---|
| 3892 |
|
|---|
| 3893 | def_optimize(AST_Class, function(self) {
|
|---|
| 3894 | for (let i = 0; i < self.properties.length; i++) {
|
|---|
| 3895 | const prop = self.properties[i];
|
|---|
| 3896 | if (prop instanceof AST_ClassStaticBlock && prop.body.length == 0) {
|
|---|
| 3897 | self.properties.splice(i, 1);
|
|---|
| 3898 | i--;
|
|---|
| 3899 | }
|
|---|
| 3900 | }
|
|---|
| 3901 |
|
|---|
| 3902 | return self;
|
|---|
| 3903 | });
|
|---|
| 3904 |
|
|---|
| 3905 | def_optimize(AST_ClassStaticBlock, function(self, compressor) {
|
|---|
| 3906 | tighten_body(self.body, compressor);
|
|---|
| 3907 | return self;
|
|---|
| 3908 | });
|
|---|
| 3909 |
|
|---|
| 3910 | def_optimize(AST_Yield, function(self, compressor) {
|
|---|
| 3911 | if (self.expression && !self.is_star && is_undefined(self.expression, compressor)) {
|
|---|
| 3912 | self.expression = null;
|
|---|
| 3913 | }
|
|---|
| 3914 | return self;
|
|---|
| 3915 | });
|
|---|
| 3916 |
|
|---|
| 3917 | def_optimize(AST_TemplateString, function(self, compressor) {
|
|---|
| 3918 | if (
|
|---|
| 3919 | !compressor.option("evaluate")
|
|---|
| 3920 | || compressor.parent() instanceof AST_PrefixedTemplateString
|
|---|
| 3921 | ) {
|
|---|
| 3922 | return self;
|
|---|
| 3923 | }
|
|---|
| 3924 |
|
|---|
| 3925 | var segments = [];
|
|---|
| 3926 | for (var i = 0; i < self.segments.length; i++) {
|
|---|
| 3927 | var segment = self.segments[i];
|
|---|
| 3928 | if (segment instanceof AST_Node) {
|
|---|
| 3929 | var result = segment.evaluate(compressor);
|
|---|
| 3930 | // Evaluate to constant value
|
|---|
| 3931 | // Constant value shorter than ${segment}
|
|---|
| 3932 | if (result !== segment && (result + "").length <= segment.size() + "${}".length) {
|
|---|
| 3933 | // There should always be a previous and next segment if segment is a node
|
|---|
| 3934 | segments[segments.length - 1].value = segments[segments.length - 1].value + result + self.segments[++i].value;
|
|---|
| 3935 | continue;
|
|---|
| 3936 | }
|
|---|
| 3937 | // `before ${`innerBefore ${any} innerAfter`} after` => `before innerBefore ${any} innerAfter after`
|
|---|
| 3938 | // TODO:
|
|---|
| 3939 | // `before ${'test' + foo} after` => `before innerBefore ${any} innerAfter after`
|
|---|
| 3940 | // `before ${foo + 'test} after` => `before innerBefore ${any} innerAfter after`
|
|---|
| 3941 | if (segment instanceof AST_TemplateString) {
|
|---|
| 3942 | var inners = segment.segments;
|
|---|
| 3943 | segments[segments.length - 1].value += inners[0].value;
|
|---|
| 3944 | for (var j = 1; j < inners.length; j++) {
|
|---|
| 3945 | segment = inners[j];
|
|---|
| 3946 | segments.push(segment);
|
|---|
| 3947 | }
|
|---|
| 3948 | continue;
|
|---|
| 3949 | }
|
|---|
| 3950 | }
|
|---|
| 3951 | segments.push(segment);
|
|---|
| 3952 | }
|
|---|
| 3953 | self.segments = segments;
|
|---|
| 3954 |
|
|---|
| 3955 | // `foo` => "foo"
|
|---|
| 3956 | if (segments.length == 1) {
|
|---|
| 3957 | return make_node(AST_String, self, segments[0]);
|
|---|
| 3958 | }
|
|---|
| 3959 |
|
|---|
| 3960 | if (
|
|---|
| 3961 | segments.length === 3
|
|---|
| 3962 | && segments[1] instanceof AST_Node
|
|---|
| 3963 | && (
|
|---|
| 3964 | segments[1].is_string(compressor)
|
|---|
| 3965 | || segments[1].is_number_or_bigint(compressor)
|
|---|
| 3966 | || is_nullish(segments[1], compressor)
|
|---|
| 3967 | || compressor.option("unsafe")
|
|---|
| 3968 | )
|
|---|
| 3969 | ) {
|
|---|
| 3970 | // `foo${bar}` => "foo" + bar
|
|---|
| 3971 | if (segments[2].value === "") {
|
|---|
| 3972 | return make_node(AST_Binary, self, {
|
|---|
| 3973 | operator: "+",
|
|---|
| 3974 | left: make_node(AST_String, self, {
|
|---|
| 3975 | value: segments[0].value,
|
|---|
| 3976 | }),
|
|---|
| 3977 | right: segments[1],
|
|---|
| 3978 | });
|
|---|
| 3979 | }
|
|---|
| 3980 | // `${bar}baz` => bar + "baz"
|
|---|
| 3981 | if (segments[0].value === "") {
|
|---|
| 3982 | return make_node(AST_Binary, self, {
|
|---|
| 3983 | operator: "+",
|
|---|
| 3984 | left: segments[1],
|
|---|
| 3985 | right: make_node(AST_String, self, {
|
|---|
| 3986 | value: segments[2].value,
|
|---|
| 3987 | }),
|
|---|
| 3988 | });
|
|---|
| 3989 | }
|
|---|
| 3990 | }
|
|---|
| 3991 | return self;
|
|---|
| 3992 | });
|
|---|
| 3993 |
|
|---|
| 3994 | def_optimize(AST_PrefixedTemplateString, function(self) {
|
|---|
| 3995 | return self;
|
|---|
| 3996 | });
|
|---|
| 3997 |
|
|---|
| 3998 | // ["p"]:1 ---> p:1
|
|---|
| 3999 | // [42]:1 ---> 42:1
|
|---|
| 4000 | function lift_key(self, compressor) {
|
|---|
| 4001 | if (!compressor.option("computed_props")) return self;
|
|---|
| 4002 | // save a comparison in the typical case
|
|---|
| 4003 | if (!(self.key instanceof AST_Constant)) return self;
|
|---|
| 4004 | // allow certain acceptable props as not all AST_Constants are true constants
|
|---|
| 4005 | if (self.key instanceof AST_String || self.key instanceof AST_Number) {
|
|---|
| 4006 | const key = self.key.value.toString();
|
|---|
| 4007 |
|
|---|
| 4008 | if (key === "__proto__") return self;
|
|---|
| 4009 | if (key == "constructor"
|
|---|
| 4010 | && compressor.parent() instanceof AST_Class) return self;
|
|---|
| 4011 | if (self instanceof AST_ObjectKeyVal) {
|
|---|
| 4012 | self.quote = self.key.quote;
|
|---|
| 4013 | self.key = key;
|
|---|
| 4014 | } else if (self instanceof AST_ClassProperty) {
|
|---|
| 4015 | self.quote = self.key.quote;
|
|---|
| 4016 | self.key = make_node(AST_SymbolClassProperty, self.key, {
|
|---|
| 4017 | name: key,
|
|---|
| 4018 | });
|
|---|
| 4019 | } else {
|
|---|
| 4020 | self.quote = self.key.quote;
|
|---|
| 4021 | self.key = make_node(AST_SymbolMethod, self.key, {
|
|---|
| 4022 | name: key,
|
|---|
| 4023 | });
|
|---|
| 4024 | }
|
|---|
| 4025 | }
|
|---|
| 4026 | return self;
|
|---|
| 4027 | }
|
|---|
| 4028 |
|
|---|
| 4029 | def_optimize(AST_ObjectProperty, lift_key);
|
|---|
| 4030 |
|
|---|
| 4031 | def_optimize(AST_ConciseMethod, function(self, compressor) {
|
|---|
| 4032 | lift_key(self, compressor);
|
|---|
| 4033 | // p(){return x;} ---> p:()=>x
|
|---|
| 4034 | if (compressor.option("arrows")
|
|---|
| 4035 | && compressor.parent() instanceof AST_Object
|
|---|
| 4036 | && !self.value.is_generator
|
|---|
| 4037 | && !self.value.uses_arguments
|
|---|
| 4038 | && !self.value.pinned()
|
|---|
| 4039 | && self.value.body.length == 1
|
|---|
| 4040 | && self.value.body[0] instanceof AST_Return
|
|---|
| 4041 | && self.value.body[0].value
|
|---|
| 4042 | && !self.value.contains_this()) {
|
|---|
| 4043 | var arrow = make_node(AST_Arrow, self.value, self.value);
|
|---|
| 4044 | arrow.async = self.value.async;
|
|---|
| 4045 | arrow.is_generator = self.value.is_generator;
|
|---|
| 4046 | return make_node(AST_ObjectKeyVal, self, {
|
|---|
| 4047 | key: self.key instanceof AST_SymbolMethod ? self.key.name : self.key,
|
|---|
| 4048 | value: arrow,
|
|---|
| 4049 | quote: self.quote,
|
|---|
| 4050 | });
|
|---|
| 4051 | }
|
|---|
| 4052 | return self;
|
|---|
| 4053 | });
|
|---|
| 4054 |
|
|---|
| 4055 | def_optimize(AST_ObjectKeyVal, function(self, compressor) {
|
|---|
| 4056 | lift_key(self, compressor);
|
|---|
| 4057 | // p:function(){} ---> p(){}
|
|---|
| 4058 | // p:function*(){} ---> *p(){}
|
|---|
| 4059 | // p:async function(){} ---> async p(){}
|
|---|
| 4060 | // p:()=>{} ---> p(){}
|
|---|
| 4061 | // p:async()=>{} ---> async p(){}
|
|---|
| 4062 | var unsafe_methods = compressor.option("unsafe_methods");
|
|---|
| 4063 | if (unsafe_methods
|
|---|
| 4064 | && compressor.option("ecma") >= 2015
|
|---|
| 4065 | && (!(unsafe_methods instanceof RegExp) || unsafe_methods.test(self.key + ""))) {
|
|---|
| 4066 | var key = self.key;
|
|---|
| 4067 | var value = self.value;
|
|---|
| 4068 | var is_arrow_with_block = value instanceof AST_Arrow
|
|---|
| 4069 | && Array.isArray(value.body)
|
|---|
| 4070 | && !value.contains_this();
|
|---|
| 4071 | if ((is_arrow_with_block || value instanceof AST_Function) && !value.name) {
|
|---|
| 4072 | return make_node(AST_ConciseMethod, self, {
|
|---|
| 4073 | key: key instanceof AST_Node ? key : make_node(AST_SymbolMethod, self, {
|
|---|
| 4074 | name: key,
|
|---|
| 4075 | }),
|
|---|
| 4076 | value: make_node(AST_Accessor, value, value),
|
|---|
| 4077 | quote: self.quote,
|
|---|
| 4078 | });
|
|---|
| 4079 | }
|
|---|
| 4080 | }
|
|---|
| 4081 | return self;
|
|---|
| 4082 | });
|
|---|
| 4083 |
|
|---|
| 4084 | def_optimize(AST_Destructuring, function(self, compressor) {
|
|---|
| 4085 | if (compressor.option("pure_getters") == true
|
|---|
| 4086 | && compressor.option("unused")
|
|---|
| 4087 | && !self.is_array
|
|---|
| 4088 | && Array.isArray(self.names)
|
|---|
| 4089 | && !is_destructuring_export_decl(compressor)
|
|---|
| 4090 | && !(self.names[self.names.length - 1] instanceof AST_Expansion)) {
|
|---|
| 4091 | var keep = [];
|
|---|
| 4092 | for (var i = 0; i < self.names.length; i++) {
|
|---|
| 4093 | var elem = self.names[i];
|
|---|
| 4094 | if (!(elem instanceof AST_ObjectKeyVal
|
|---|
| 4095 | && typeof elem.key == "string"
|
|---|
| 4096 | && elem.value instanceof AST_SymbolDeclaration
|
|---|
| 4097 | && !should_retain(compressor, elem.value.definition()))) {
|
|---|
| 4098 | keep.push(elem);
|
|---|
| 4099 | }
|
|---|
| 4100 | }
|
|---|
| 4101 | if (keep.length != self.names.length) {
|
|---|
| 4102 | self.names = keep;
|
|---|
| 4103 | }
|
|---|
| 4104 | }
|
|---|
| 4105 | return self;
|
|---|
| 4106 |
|
|---|
| 4107 | function is_destructuring_export_decl(compressor) {
|
|---|
| 4108 | var ancestors = [/^VarDef$/, /^(Const|Let|Var)$/, /^Export$/];
|
|---|
| 4109 | for (var a = 0, p = 0, len = ancestors.length; a < len; p++) {
|
|---|
| 4110 | var parent = compressor.parent(p);
|
|---|
| 4111 | if (!parent) return false;
|
|---|
| 4112 | if (a === 0 && parent.TYPE == "Destructuring") continue;
|
|---|
| 4113 | if (!ancestors[a].test(parent.TYPE)) {
|
|---|
| 4114 | return false;
|
|---|
| 4115 | }
|
|---|
| 4116 | a++;
|
|---|
| 4117 | }
|
|---|
| 4118 | return true;
|
|---|
| 4119 | }
|
|---|
| 4120 |
|
|---|
| 4121 | function should_retain(compressor, def) {
|
|---|
| 4122 | if (def.references.length) return true;
|
|---|
| 4123 | if (!def.global) return false;
|
|---|
| 4124 | if (compressor.toplevel.vars) {
|
|---|
| 4125 | if (compressor.top_retain) {
|
|---|
| 4126 | return compressor.top_retain(def);
|
|---|
| 4127 | }
|
|---|
| 4128 | return false;
|
|---|
| 4129 | }
|
|---|
| 4130 | return true;
|
|---|
| 4131 | }
|
|---|
| 4132 | });
|
|---|
| 4133 |
|
|---|
| 4134 | export {
|
|---|
| 4135 | Compressor,
|
|---|
| 4136 | };
|
|---|