| 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 | HOP,
|
|---|
| 46 | makePredicate,
|
|---|
| 47 | return_this,
|
|---|
| 48 | string_template,
|
|---|
| 49 | regexp_source_fix,
|
|---|
| 50 | regexp_is_safe,
|
|---|
| 51 | } from "../utils/index.js";
|
|---|
| 52 | import {
|
|---|
| 53 | AST_Array,
|
|---|
| 54 | AST_BigInt,
|
|---|
| 55 | AST_Binary,
|
|---|
| 56 | AST_Call,
|
|---|
| 57 | AST_Chain,
|
|---|
| 58 | AST_Class,
|
|---|
| 59 | AST_Conditional,
|
|---|
| 60 | AST_Constant,
|
|---|
| 61 | AST_Dot,
|
|---|
| 62 | AST_Expansion,
|
|---|
| 63 | AST_Function,
|
|---|
| 64 | AST_Lambda,
|
|---|
| 65 | AST_New,
|
|---|
| 66 | AST_Node,
|
|---|
| 67 | AST_Object,
|
|---|
| 68 | AST_PropAccess,
|
|---|
| 69 | AST_RegExp,
|
|---|
| 70 | AST_Statement,
|
|---|
| 71 | AST_Symbol,
|
|---|
| 72 | AST_SymbolRef,
|
|---|
| 73 | AST_TemplateString,
|
|---|
| 74 | AST_UnaryPrefix,
|
|---|
| 75 | AST_With,
|
|---|
| 76 | } from "../ast.js";
|
|---|
| 77 | import { is_undeclared_ref} from "./inference.js";
|
|---|
| 78 |
|
|---|
| 79 | // methods to evaluate a constant expression
|
|---|
| 80 |
|
|---|
| 81 | function def_eval(node, func) {
|
|---|
| 82 | node.DEFMETHOD("_eval", func);
|
|---|
| 83 | }
|
|---|
| 84 |
|
|---|
| 85 | // Used to propagate a nullish short-circuit signal upwards through the chain.
|
|---|
| 86 | export const nullish = Symbol("This AST_Chain is nullish");
|
|---|
| 87 |
|
|---|
| 88 | // If the node has been successfully reduced to a constant,
|
|---|
| 89 | // then its value is returned; otherwise the element itself
|
|---|
| 90 | // is returned.
|
|---|
| 91 | // They can be distinguished as constant value is never a
|
|---|
| 92 | // descendant of AST_Node.
|
|---|
| 93 | AST_Node.DEFMETHOD("evaluate", function (compressor) {
|
|---|
| 94 | if (!compressor.option("evaluate"))
|
|---|
| 95 | return this;
|
|---|
| 96 | var val = this._eval(compressor, 1);
|
|---|
| 97 | if (!val || val instanceof RegExp)
|
|---|
| 98 | return val;
|
|---|
| 99 | if (typeof val == "function" || typeof val == "object" || val == nullish)
|
|---|
| 100 | return this;
|
|---|
| 101 |
|
|---|
| 102 | // Evaluated strings can be larger than the original expression
|
|---|
| 103 | if (typeof val === "string") {
|
|---|
| 104 | const unevaluated_size = this.size(compressor);
|
|---|
| 105 | if (val.length + 2 > unevaluated_size) return this;
|
|---|
| 106 | }
|
|---|
| 107 |
|
|---|
| 108 | return val;
|
|---|
| 109 | });
|
|---|
| 110 |
|
|---|
| 111 | var unaryPrefix = makePredicate("! ~ - + void");
|
|---|
| 112 | AST_Node.DEFMETHOD("is_constant", function () {
|
|---|
| 113 | // Accomodate when compress option evaluate=false
|
|---|
| 114 | // as well as the common constant expressions !0 and -1
|
|---|
| 115 | if (this instanceof AST_Constant) {
|
|---|
| 116 | return !(this instanceof AST_RegExp);
|
|---|
| 117 | } else {
|
|---|
| 118 | return this instanceof AST_UnaryPrefix
|
|---|
| 119 | && unaryPrefix.has(this.operator)
|
|---|
| 120 | && (
|
|---|
| 121 | // `this.expression` may be an `AST_RegExp`,
|
|---|
| 122 | // so not only `.is_constant()`.
|
|---|
| 123 | this.expression instanceof AST_Constant
|
|---|
| 124 | || this.expression.is_constant()
|
|---|
| 125 | );
|
|---|
| 126 | }
|
|---|
| 127 | });
|
|---|
| 128 |
|
|---|
| 129 | def_eval(AST_Statement, function () {
|
|---|
| 130 | throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
|
|---|
| 131 | });
|
|---|
| 132 |
|
|---|
| 133 | def_eval(AST_Lambda, return_this);
|
|---|
| 134 | def_eval(AST_Class, return_this);
|
|---|
| 135 | def_eval(AST_Node, return_this);
|
|---|
| 136 | def_eval(AST_Constant, function () {
|
|---|
| 137 | return this.getValue();
|
|---|
| 138 | });
|
|---|
| 139 |
|
|---|
| 140 | const supports_bigint = typeof BigInt === "function";
|
|---|
| 141 | def_eval(AST_BigInt, function () {
|
|---|
| 142 | if (supports_bigint) {
|
|---|
| 143 | return BigInt(this.value);
|
|---|
| 144 | } else {
|
|---|
| 145 | return this;
|
|---|
| 146 | }
|
|---|
| 147 | });
|
|---|
| 148 |
|
|---|
| 149 | def_eval(AST_RegExp, function (compressor) {
|
|---|
| 150 | let evaluated = compressor.evaluated_regexps.get(this.value);
|
|---|
| 151 | if (evaluated === undefined && regexp_is_safe(this.value.source)) {
|
|---|
| 152 | try {
|
|---|
| 153 | const { source, flags } = this.value;
|
|---|
| 154 | evaluated = new RegExp(source, flags);
|
|---|
| 155 | } catch (e) {
|
|---|
| 156 | evaluated = null;
|
|---|
| 157 | }
|
|---|
| 158 | compressor.evaluated_regexps.set(this.value, evaluated);
|
|---|
| 159 | }
|
|---|
| 160 | return evaluated || this;
|
|---|
| 161 | });
|
|---|
| 162 |
|
|---|
| 163 | def_eval(AST_TemplateString, function () {
|
|---|
| 164 | if (this.segments.length !== 1) return this;
|
|---|
| 165 | return this.segments[0].value;
|
|---|
| 166 | });
|
|---|
| 167 |
|
|---|
| 168 | def_eval(AST_Function, function (compressor) {
|
|---|
| 169 | if (compressor.option("unsafe")) {
|
|---|
| 170 | var fn = function () { };
|
|---|
| 171 | fn.node = this;
|
|---|
| 172 | fn.toString = () => this.print_to_string();
|
|---|
| 173 | return fn;
|
|---|
| 174 | }
|
|---|
| 175 | return this;
|
|---|
| 176 | });
|
|---|
| 177 |
|
|---|
| 178 | def_eval(AST_Array, function (compressor, depth) {
|
|---|
| 179 | if (compressor.option("unsafe")) {
|
|---|
| 180 | var elements = [];
|
|---|
| 181 | for (var i = 0, len = this.elements.length; i < len; i++) {
|
|---|
| 182 | var element = this.elements[i];
|
|---|
| 183 | var value = element._eval(compressor, depth);
|
|---|
| 184 | if (element === value)
|
|---|
| 185 | return this;
|
|---|
| 186 | elements.push(value);
|
|---|
| 187 | }
|
|---|
| 188 | return elements;
|
|---|
| 189 | }
|
|---|
| 190 | return this;
|
|---|
| 191 | });
|
|---|
| 192 |
|
|---|
| 193 | def_eval(AST_Object, function (compressor, depth) {
|
|---|
| 194 | if (compressor.option("unsafe")) {
|
|---|
| 195 | var val = {};
|
|---|
| 196 | for (var i = 0, len = this.properties.length; i < len; i++) {
|
|---|
| 197 | var prop = this.properties[i];
|
|---|
| 198 | if (prop instanceof AST_Expansion)
|
|---|
| 199 | return this;
|
|---|
| 200 | var key = prop.key;
|
|---|
| 201 | if (key instanceof AST_Symbol) {
|
|---|
| 202 | key = key.name;
|
|---|
| 203 | } else if (key instanceof AST_Node) {
|
|---|
| 204 | key = key._eval(compressor, depth);
|
|---|
| 205 | if (key === prop.key)
|
|---|
| 206 | return this;
|
|---|
| 207 | }
|
|---|
| 208 | if (typeof Object.prototype[key] === "function") {
|
|---|
| 209 | return this;
|
|---|
| 210 | }
|
|---|
| 211 | if (prop.value instanceof AST_Function)
|
|---|
| 212 | continue;
|
|---|
| 213 | val[key] = prop.value._eval(compressor, depth);
|
|---|
| 214 | if (val[key] === prop.value)
|
|---|
| 215 | return this;
|
|---|
| 216 | }
|
|---|
| 217 | return val;
|
|---|
| 218 | }
|
|---|
| 219 | return this;
|
|---|
| 220 | });
|
|---|
| 221 |
|
|---|
| 222 | var non_converting_unary = makePredicate("! typeof void");
|
|---|
| 223 | def_eval(AST_UnaryPrefix, function (compressor, depth) {
|
|---|
| 224 | var e = this.expression;
|
|---|
| 225 | if (compressor.option("typeofs")
|
|---|
| 226 | && this.operator == "typeof") {
|
|---|
| 227 | // Function would be evaluated to an array and so typeof would
|
|---|
| 228 | // incorrectly return 'object'. Hence making is a special case.
|
|---|
| 229 | if (e instanceof AST_Lambda
|
|---|
| 230 | || e instanceof AST_SymbolRef
|
|---|
| 231 | && e.fixed_value() instanceof AST_Lambda) {
|
|---|
| 232 | return typeof function () { };
|
|---|
| 233 | }
|
|---|
| 234 | if (
|
|---|
| 235 | (e instanceof AST_Object
|
|---|
| 236 | || e instanceof AST_Array
|
|---|
| 237 | || (e instanceof AST_SymbolRef
|
|---|
| 238 | && (e.fixed_value() instanceof AST_Object
|
|---|
| 239 | || e.fixed_value() instanceof AST_Array)))
|
|---|
| 240 | && !e.has_side_effects(compressor)
|
|---|
| 241 | ) {
|
|---|
| 242 | return typeof {};
|
|---|
| 243 | }
|
|---|
| 244 | }
|
|---|
| 245 | if (!non_converting_unary.has(this.operator))
|
|---|
| 246 | depth++;
|
|---|
| 247 | e = e._eval(compressor, depth);
|
|---|
| 248 | if (e === this.expression)
|
|---|
| 249 | return this;
|
|---|
| 250 | switch (this.operator) {
|
|---|
| 251 | case "!": return !e;
|
|---|
| 252 | case "typeof":
|
|---|
| 253 | // typeof <RegExp> returns "object" or "function" on different platforms
|
|---|
| 254 | // so cannot evaluate reliably
|
|---|
| 255 | if (e instanceof RegExp)
|
|---|
| 256 | return this;
|
|---|
| 257 | return typeof e;
|
|---|
| 258 | case "void": return void e;
|
|---|
| 259 | case "~": return ~e;
|
|---|
| 260 | case "-": return -e;
|
|---|
| 261 | case "+": return +e;
|
|---|
| 262 | }
|
|---|
| 263 | return this;
|
|---|
| 264 | });
|
|---|
| 265 |
|
|---|
| 266 | var non_converting_binary = makePredicate("&& || ?? === !==");
|
|---|
| 267 | const identity_comparison = makePredicate("== != === !==");
|
|---|
| 268 | const has_identity = value => typeof value === "object"
|
|---|
| 269 | || typeof value === "function"
|
|---|
| 270 | || typeof value === "symbol";
|
|---|
| 271 |
|
|---|
| 272 | def_eval(AST_Binary, function (compressor, depth) {
|
|---|
| 273 | if (!non_converting_binary.has(this.operator))
|
|---|
| 274 | depth++;
|
|---|
| 275 |
|
|---|
| 276 | var left = this.left._eval(compressor, depth);
|
|---|
| 277 | if (left === this.left)
|
|---|
| 278 | return this;
|
|---|
| 279 | var right = this.right._eval(compressor, depth);
|
|---|
| 280 | if (right === this.right)
|
|---|
| 281 | return this;
|
|---|
| 282 |
|
|---|
| 283 | if (left != null
|
|---|
| 284 | && right != null
|
|---|
| 285 | && identity_comparison.has(this.operator)
|
|---|
| 286 | && has_identity(left)
|
|---|
| 287 | && has_identity(right)
|
|---|
| 288 | && typeof left === typeof right) {
|
|---|
| 289 | // Do not compare by reference
|
|---|
| 290 | return this;
|
|---|
| 291 | }
|
|---|
| 292 |
|
|---|
| 293 | // Do not mix BigInt and Number; Don't use `>>>` on BigInt or `/ 0n`
|
|---|
| 294 | if (
|
|---|
| 295 | (typeof left === "bigint") !== (typeof right === "bigint")
|
|---|
| 296 | || typeof left === "bigint"
|
|---|
| 297 | && (this.operator === ">>>"
|
|---|
| 298 | || this.operator === "/" && Number(right) === 0)
|
|---|
| 299 | ) {
|
|---|
| 300 | return this;
|
|---|
| 301 | }
|
|---|
| 302 |
|
|---|
| 303 | var result;
|
|---|
| 304 | switch (this.operator) {
|
|---|
| 305 | case "&&": result = left && right; break;
|
|---|
| 306 | case "||": result = left || right; break;
|
|---|
| 307 | case "??": result = left != null ? left : right; break;
|
|---|
| 308 | case "|": result = left | right; break;
|
|---|
| 309 | case "&": result = left & right; break;
|
|---|
| 310 | case "^": result = left ^ right; break;
|
|---|
| 311 | case "+": result = left + right; break;
|
|---|
| 312 | case "*": result = left * right; break;
|
|---|
| 313 | case "**": result = left ** right; break;
|
|---|
| 314 | case "/": result = left / right; break;
|
|---|
| 315 | case "%": result = left % right; break;
|
|---|
| 316 | case "-": result = left - right; break;
|
|---|
| 317 | case "<<": result = left << right; break;
|
|---|
| 318 | case ">>": result = left >> right; break;
|
|---|
| 319 | case ">>>": result = left >>> right; break;
|
|---|
| 320 | case "==": result = left == right; break;
|
|---|
| 321 | case "===": result = left === right; break;
|
|---|
| 322 | case "!=": result = left != right; break;
|
|---|
| 323 | case "!==": result = left !== right; break;
|
|---|
| 324 | case "<": result = left < right; break;
|
|---|
| 325 | case "<=": result = left <= right; break;
|
|---|
| 326 | case ">": result = left > right; break;
|
|---|
| 327 | case ">=": result = left >= right; break;
|
|---|
| 328 | default:
|
|---|
| 329 | return this;
|
|---|
| 330 | }
|
|---|
| 331 | if (typeof result === "number" && isNaN(result) && compressor.find_parent(AST_With)) {
|
|---|
| 332 | // leave original expression as is
|
|---|
| 333 | return this;
|
|---|
| 334 | }
|
|---|
| 335 | return result;
|
|---|
| 336 | });
|
|---|
| 337 |
|
|---|
| 338 | def_eval(AST_Conditional, function (compressor, depth) {
|
|---|
| 339 | var condition = this.condition._eval(compressor, depth);
|
|---|
| 340 | if (condition === this.condition)
|
|---|
| 341 | return this;
|
|---|
| 342 | var node = condition ? this.consequent : this.alternative;
|
|---|
| 343 | var value = node._eval(compressor, depth);
|
|---|
| 344 | return value === node ? this : value;
|
|---|
| 345 | });
|
|---|
| 346 |
|
|---|
| 347 | // Set of AST_SymbolRef which are currently being evaluated.
|
|---|
| 348 | // Avoids infinite recursion of ._eval()
|
|---|
| 349 | const reentrant_ref_eval = new Set();
|
|---|
| 350 | def_eval(AST_SymbolRef, function (compressor, depth) {
|
|---|
| 351 | if (reentrant_ref_eval.has(this))
|
|---|
| 352 | return this;
|
|---|
| 353 |
|
|---|
| 354 | var fixed = this.fixed_value();
|
|---|
| 355 | if (!fixed)
|
|---|
| 356 | return this;
|
|---|
| 357 |
|
|---|
| 358 | reentrant_ref_eval.add(this);
|
|---|
| 359 | const value = fixed._eval(compressor, depth);
|
|---|
| 360 | reentrant_ref_eval.delete(this);
|
|---|
| 361 |
|
|---|
| 362 | if (value === fixed)
|
|---|
| 363 | return this;
|
|---|
| 364 |
|
|---|
| 365 | if (value && typeof value == "object") {
|
|---|
| 366 | var escaped = this.definition().escaped;
|
|---|
| 367 | if (escaped && depth > escaped)
|
|---|
| 368 | return this;
|
|---|
| 369 | }
|
|---|
| 370 | return value;
|
|---|
| 371 | });
|
|---|
| 372 |
|
|---|
| 373 | def_eval(AST_Chain, function (compressor, depth) {
|
|---|
| 374 | const evaluated = this.expression._eval(compressor, depth, /*ast_chain=*/true);
|
|---|
| 375 | return evaluated === nullish
|
|---|
| 376 | ? undefined
|
|---|
| 377 | : evaluated === this.expression
|
|---|
| 378 | ? this
|
|---|
| 379 | : evaluated;
|
|---|
| 380 | });
|
|---|
| 381 |
|
|---|
| 382 | const global_objs = { Array, Math, Number, Object, String };
|
|---|
| 383 |
|
|---|
| 384 | const regexp_flags = new Set([
|
|---|
| 385 | "dotAll",
|
|---|
| 386 | "global",
|
|---|
| 387 | "ignoreCase",
|
|---|
| 388 | "multiline",
|
|---|
| 389 | "sticky",
|
|---|
| 390 | "unicode",
|
|---|
| 391 | ]);
|
|---|
| 392 |
|
|---|
| 393 | def_eval(AST_PropAccess, function (compressor, depth, ast_chain) {
|
|---|
| 394 | let obj = (ast_chain || this.property === "length" || compressor.option("unsafe"))
|
|---|
| 395 | && this.expression._eval(compressor, depth + 1, ast_chain);
|
|---|
| 396 |
|
|---|
| 397 | if (ast_chain) {
|
|---|
| 398 | if (obj === nullish || (this.optional && obj == null)) return nullish;
|
|---|
| 399 | }
|
|---|
| 400 |
|
|---|
| 401 | // `.length` of strings and arrays is always safe
|
|---|
| 402 | if (this.property === "length") {
|
|---|
| 403 | if (typeof obj === "string") {
|
|---|
| 404 | return obj.length;
|
|---|
| 405 | }
|
|---|
| 406 |
|
|---|
| 407 | const is_spreadless_array =
|
|---|
| 408 | obj instanceof AST_Array
|
|---|
| 409 | && obj.elements.every(el => !(el instanceof AST_Expansion));
|
|---|
| 410 |
|
|---|
| 411 | if (
|
|---|
| 412 | is_spreadless_array
|
|---|
| 413 | && obj.elements.every(el => !el.has_side_effects(compressor))
|
|---|
| 414 | ) {
|
|---|
| 415 | return obj.elements.length;
|
|---|
| 416 | }
|
|---|
| 417 | }
|
|---|
| 418 |
|
|---|
| 419 | if (compressor.option("unsafe")) {
|
|---|
| 420 | var key = this.property;
|
|---|
| 421 | if (key instanceof AST_Node) {
|
|---|
| 422 | key = key._eval(compressor, depth);
|
|---|
| 423 | if (key === this.property)
|
|---|
| 424 | return this;
|
|---|
| 425 | }
|
|---|
| 426 |
|
|---|
| 427 | var exp = this.expression;
|
|---|
| 428 | if (is_undeclared_ref(exp)) {
|
|---|
| 429 | var aa;
|
|---|
| 430 | var first_arg = exp.name === "hasOwnProperty"
|
|---|
| 431 | && key === "call"
|
|---|
| 432 | && (aa = compressor.parent() && compressor.parent().args)
|
|---|
| 433 | && (aa && aa[0]
|
|---|
| 434 | && aa[0].evaluate(compressor));
|
|---|
| 435 |
|
|---|
| 436 | first_arg = first_arg instanceof AST_Dot ? first_arg.expression : first_arg;
|
|---|
| 437 |
|
|---|
| 438 | if (first_arg == null || first_arg.thedef && first_arg.thedef.undeclared) {
|
|---|
| 439 | return this.clone();
|
|---|
| 440 | }
|
|---|
| 441 | if (!compressor.is_pure_native_static_property(exp.name, key))
|
|---|
| 442 | return this;
|
|---|
| 443 | obj = global_objs[exp.name];
|
|---|
| 444 | } else {
|
|---|
| 445 | if (obj instanceof RegExp) {
|
|---|
| 446 | if (key == "source") {
|
|---|
| 447 | return regexp_source_fix(obj.source);
|
|---|
| 448 | } else if (key == "flags" || regexp_flags.has(key)) {
|
|---|
| 449 | return obj[key];
|
|---|
| 450 | }
|
|---|
| 451 | }
|
|---|
| 452 | if (!obj || obj === exp || !HOP(obj, key))
|
|---|
| 453 | return this;
|
|---|
| 454 |
|
|---|
| 455 | if (typeof obj == "function")
|
|---|
| 456 | switch (key) {
|
|---|
| 457 | case "name":
|
|---|
| 458 | return obj.node.name ? obj.node.name.name : "";
|
|---|
| 459 | case "length":
|
|---|
| 460 | return obj.node.length_property();
|
|---|
| 461 | default:
|
|---|
| 462 | return this;
|
|---|
| 463 | }
|
|---|
| 464 | }
|
|---|
| 465 | return obj[key];
|
|---|
| 466 | }
|
|---|
| 467 | return this;
|
|---|
| 468 | });
|
|---|
| 469 |
|
|---|
| 470 | def_eval(AST_Call, function (compressor, depth, ast_chain) {
|
|---|
| 471 | var exp = this.expression;
|
|---|
| 472 |
|
|---|
| 473 | if (ast_chain) {
|
|---|
| 474 | const callee = exp._eval(compressor, depth, ast_chain);
|
|---|
| 475 | if (callee === nullish || (this.optional && callee == null)) return nullish;
|
|---|
| 476 | }
|
|---|
| 477 |
|
|---|
| 478 | if (compressor.option("unsafe") && exp instanceof AST_PropAccess) {
|
|---|
| 479 | var key = exp.property;
|
|---|
| 480 | if (key instanceof AST_Node) {
|
|---|
| 481 | key = key._eval(compressor, depth);
|
|---|
| 482 | if (typeof key !== "string" && typeof key !== "number")
|
|---|
| 483 | return this;
|
|---|
| 484 | }
|
|---|
| 485 | var val;
|
|---|
| 486 | var e = exp.expression;
|
|---|
| 487 | if (is_undeclared_ref(e)) {
|
|---|
| 488 | var first_arg = e.name === "hasOwnProperty" &&
|
|---|
| 489 | key === "call" &&
|
|---|
| 490 | (this.args[0] && this.args[0].evaluate(compressor));
|
|---|
| 491 |
|
|---|
| 492 | first_arg = first_arg instanceof AST_Dot ? first_arg.expression : first_arg;
|
|---|
| 493 |
|
|---|
| 494 | if ((first_arg == null || first_arg.thedef && first_arg.thedef.undeclared)) {
|
|---|
| 495 | return this.clone();
|
|---|
| 496 | }
|
|---|
| 497 | if (!compressor.is_pure_native_static_fn(e.name, key)) return this;
|
|---|
| 498 | val = global_objs[e.name];
|
|---|
| 499 | } else {
|
|---|
| 500 | val = e._eval(compressor, depth + 1, /* don't pass ast_chain (exponential work) */);
|
|---|
| 501 |
|
|---|
| 502 | if (val === e || !val)
|
|---|
| 503 | return this;
|
|---|
| 504 | if (!compressor.is_pure_native_method(val.constructor.name, key))
|
|---|
| 505 | return this;
|
|---|
| 506 | }
|
|---|
| 507 | var args = [];
|
|---|
| 508 | for (var i = 0, len = this.args.length; i < len; i++) {
|
|---|
| 509 | var arg = this.args[i];
|
|---|
| 510 | var value = arg._eval(compressor, depth);
|
|---|
| 511 | if (arg === value)
|
|---|
| 512 | return this;
|
|---|
| 513 | if (arg instanceof AST_Lambda)
|
|---|
| 514 | return this;
|
|---|
| 515 | args.push(value);
|
|---|
| 516 | }
|
|---|
| 517 | try {
|
|---|
| 518 | return val[key].apply(val, args);
|
|---|
| 519 | } catch (ex) {
|
|---|
| 520 | // We don't really care
|
|---|
| 521 | }
|
|---|
| 522 | }
|
|---|
| 523 | return this;
|
|---|
| 524 | });
|
|---|
| 525 |
|
|---|
| 526 | // Also a subclass of AST_Call
|
|---|
| 527 | def_eval(AST_New, return_this);
|
|---|