source: frontend/node_modules/terser/lib/compress/evaluate.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 16.8 KB
RevLine 
[9af201e]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
44import {
45 HOP,
46 makePredicate,
47 return_this,
48 string_template,
49 regexp_source_fix,
50 regexp_is_safe,
51} from "../utils/index.js";
52import {
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";
77import { is_undeclared_ref} from "./inference.js";
78
79// methods to evaluate a constant expression
80
81function def_eval(node, func) {
82 node.DEFMETHOD("_eval", func);
83}
84
85// Used to propagate a nullish short-circuit signal upwards through the chain.
86export 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.
93AST_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
111var unaryPrefix = makePredicate("! ~ - + void");
112AST_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
129def_eval(AST_Statement, function () {
130 throw new Error(string_template("Cannot evaluate a statement [{file}:{line},{col}]", this.start));
131});
132
133def_eval(AST_Lambda, return_this);
134def_eval(AST_Class, return_this);
135def_eval(AST_Node, return_this);
136def_eval(AST_Constant, function () {
137 return this.getValue();
138});
139
140const supports_bigint = typeof BigInt === "function";
141def_eval(AST_BigInt, function () {
142 if (supports_bigint) {
143 return BigInt(this.value);
144 } else {
145 return this;
146 }
147});
148
149def_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
163def_eval(AST_TemplateString, function () {
164 if (this.segments.length !== 1) return this;
165 return this.segments[0].value;
166});
167
168def_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
178def_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
193def_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
222var non_converting_unary = makePredicate("! typeof void");
223def_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
266var non_converting_binary = makePredicate("&& || ?? === !==");
267const identity_comparison = makePredicate("== != === !==");
268const has_identity = value => typeof value === "object"
269 || typeof value === "function"
270 || typeof value === "symbol";
271
272def_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
338def_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()
349const reentrant_ref_eval = new Set();
350def_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
373def_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
382const global_objs = { Array, Math, Number, Object, String };
383
384const regexp_flags = new Set([
385 "dotAll",
386 "global",
387 "ignoreCase",
388 "multiline",
389 "sticky",
390 "unicode",
391]);
392
393def_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
470def_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
527def_eval(AST_New, return_this);
Note: See TracBrowser for help on using the repository browser.