source: imaps-frontend/node_modules/terser/lib/compress/evaluate.js@ 79a0317

main
Last change on this file since 79a0317 was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

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