source: frontend/node_modules/terser/lib/compress/inference.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: 41.5 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 AST_Array,
46 AST_Arrow,
47 AST_Assign,
48 AST_BigInt,
49 AST_Binary,
50 AST_Block,
51 AST_BlockStatement,
52 AST_Call,
53 AST_Case,
54 AST_Chain,
55 AST_Class,
56 AST_DefClass,
57 AST_ClassStaticBlock,
58 AST_ClassPrivateProperty,
59 AST_ClassProperty,
60 AST_ConciseMethod,
61 AST_Conditional,
62 AST_Constant,
63 AST_Definitions,
64 AST_Dot,
65 AST_EmptyStatement,
66 AST_Expansion,
67 AST_False,
68 AST_ForIn,
69 AST_Function,
70 AST_If,
71 AST_Import,
72 AST_DynamicImport,
73 AST_ImportMeta,
74 AST_Jump,
75 AST_LabeledStatement,
76 AST_Lambda,
77 AST_New,
78 AST_Node,
79 AST_Null,
80 AST_Number,
81 AST_Object,
82 AST_ObjectGetter,
83 AST_ObjectKeyVal,
84 AST_ObjectProperty,
85 AST_ObjectSetter,
86 AST_PrivateGetter,
87 AST_PrivateMethod,
88 AST_PrivateSetter,
89 AST_PropAccess,
90 AST_RegExp,
91 AST_Return,
92 AST_Scope,
93 AST_Sequence,
94 AST_SimpleStatement,
95 AST_Statement,
96 AST_String,
97 AST_Sub,
98 AST_Switch,
99 AST_SwitchBranch,
100 AST_SymbolClassProperty,
101 AST_SymbolDeclaration,
102 AST_SymbolRef,
103 AST_TemplateSegment,
104 AST_TemplateString,
105 AST_This,
106 AST_True,
107 AST_Try,
108 AST_Unary,
109 AST_UnaryPostfix,
110 AST_UnaryPrefix,
111 AST_Undefined,
112 AST_VarDef,
113
114 walk,
115 walk_abort,
116
117 _PURE
118} from "../ast.js";
119import {
120 makePredicate,
121 return_true,
122 return_false,
123 return_null,
124 return_this,
125 make_node,
126 member,
127 has_annotation,
128} from "../utils/index.js";
129import { make_sequence, best_of_expression, read_property, requires_sequence_to_maintain_binding } from "./common.js";
130
131import { INLINED, UNDEFINED, has_flag } from "./compressor-flags.js";
132import { is_pure_builtin_call, pure_prop_access_globals } from "./native-objects.js";
133
134// Functions and methods to infer certain facts about expressions
135// It's not always possible to be 100% sure about something just by static analysis,
136// so `true` means yes, and `false` means maybe
137
138export const is_undeclared_ref = (node) =>
139 node instanceof AST_SymbolRef && node.definition().undeclared;
140
141export const bitwise_binop = makePredicate("<<< >> << & | ^ ~");
142export const lazy_op = makePredicate("&& || ??");
143export const unary_side_effects = makePredicate("delete ++ --");
144
145// methods to determine whether an expression has a boolean result type
146(function(def_is_boolean) {
147 const unary_bool = makePredicate("! delete");
148 const binary_bool = makePredicate("in instanceof == != === !== < <= >= >");
149 def_is_boolean(AST_Node, return_false);
150 def_is_boolean(AST_UnaryPrefix, function() {
151 return unary_bool.has(this.operator);
152 });
153 def_is_boolean(AST_Binary, function() {
154 return binary_bool.has(this.operator)
155 || lazy_op.has(this.operator)
156 && this.left.is_boolean()
157 && this.right.is_boolean();
158 });
159 def_is_boolean(AST_Conditional, function() {
160 return this.consequent.is_boolean() && this.alternative.is_boolean();
161 });
162 def_is_boolean(AST_Assign, function() {
163 return this.operator == "=" && this.right.is_boolean();
164 });
165 def_is_boolean(AST_Sequence, function() {
166 return this.tail_node().is_boolean();
167 });
168 def_is_boolean(AST_True, return_true);
169 def_is_boolean(AST_False, return_true);
170})(function(node, func) {
171 node.DEFMETHOD("is_boolean", func);
172});
173
174// methods to determine if an expression has a numeric result type
175(function(def_is_number) {
176 def_is_number(AST_Node, return_false);
177 def_is_number(AST_Number, return_true);
178 const unary = makePredicate("+ - ~ ++ --");
179 def_is_number(AST_Unary, function(compressor) {
180 return unary.has(this.operator) && this.expression.is_number(compressor);
181 });
182 const numeric_ops = makePredicate("- * / % & | ^ << >> >>>");
183 def_is_number(AST_Binary, function(compressor) {
184 if (this.operator === "+") {
185 // Both sides need to be `number`. Or one is a `number` and the other is number-ish.
186 return this.left.is_number(compressor) && this.right.is_number_or_bigint(compressor)
187 || this.right.is_number(compressor) && this.left.is_number_or_bigint(compressor);
188 } else if (numeric_ops.has(this.operator)) {
189 return this.left.is_number(compressor) || this.right.is_number(compressor);
190 } else {
191 return false;
192 }
193 });
194 def_is_number(AST_Assign, function(compressor) {
195 return (this.operator === "=" || numeric_ops.has(this.operator.slice(0, -1)))
196 && this.right.is_number(compressor);
197 });
198 def_is_number(AST_Sequence, function(compressor) {
199 return this.tail_node().is_number(compressor);
200 });
201 def_is_number(AST_Conditional, function(compressor) {
202 return this.consequent.is_number(compressor) && this.alternative.is_number(compressor);
203 });
204})(function(node, func) {
205 node.DEFMETHOD("is_number", func);
206});
207
208// methods to determine if an expression returns a BigInt
209(function(def_is_bigint) {
210 def_is_bigint(AST_Node, return_false);
211 def_is_bigint(AST_BigInt, return_true);
212 const unary = makePredicate("+ - ~ ++ --");
213 def_is_bigint(AST_Unary, function(compressor) {
214 return unary.has(this.operator) && this.expression.is_bigint(compressor);
215 });
216 const numeric_ops = makePredicate("- * / % & | ^ << >>");
217 def_is_bigint(AST_Binary, function(compressor) {
218 if (this.operator === "+") {
219 return this.left.is_bigint(compressor) && this.right.is_number_or_bigint(compressor)
220 || this.right.is_bigint(compressor) && this.left.is_number_or_bigint(compressor);
221 } else if (numeric_ops.has(this.operator)) {
222 return this.left.is_bigint(compressor) || this.right.is_bigint(compressor);
223 } else {
224 return false;
225 }
226 });
227 def_is_bigint(AST_Assign, function(compressor) {
228 return (numeric_ops.has(this.operator.slice(0, -1)) || this.operator == "=")
229 && this.right.is_bigint(compressor);
230 });
231 def_is_bigint(AST_Sequence, function(compressor) {
232 return this.tail_node().is_bigint(compressor);
233 });
234 def_is_bigint(AST_Conditional, function(compressor) {
235 return this.consequent.is_bigint(compressor) && this.alternative.is_bigint(compressor);
236 });
237})(function(node, func) {
238 node.DEFMETHOD("is_bigint", func);
239});
240
241// methods to determine if an expression is a number or a bigint
242(function(def_is_number_or_bigint) {
243 def_is_number_or_bigint(AST_Node, return_false);
244 def_is_number_or_bigint(AST_Number, return_true);
245 def_is_number_or_bigint(AST_BigInt, return_true);
246 const numeric_unary_ops = makePredicate("+ - ~ ++ --");
247 def_is_number_or_bigint(AST_Unary, function(_compressor) {
248 return numeric_unary_ops.has(this.operator);
249 });
250 const numeric_ops = makePredicate("- * / % & | ^ << >>");
251 def_is_number_or_bigint(AST_Binary, function(compressor) {
252 return this.operator === "+"
253 ? this.left.is_number_or_bigint(compressor) && this.right.is_number_or_bigint(compressor)
254 : numeric_ops.has(this.operator);
255 });
256 def_is_number_or_bigint(AST_Assign, function(compressor) {
257 return numeric_ops.has(this.operator.slice(0, -1))
258 || this.operator == "=" && this.right.is_number_or_bigint(compressor);
259 });
260 def_is_number_or_bigint(AST_Sequence, function(compressor) {
261 return this.tail_node().is_number_or_bigint(compressor);
262 });
263 def_is_number_or_bigint(AST_Conditional, function(compressor) {
264 return this.consequent.is_number_or_bigint(compressor) && this.alternative.is_number_or_bigint(compressor);
265 });
266}(function (node, func) {
267 node.DEFMETHOD("is_number_or_bigint", func);
268}));
269
270
271// methods to determine if an expression is a 32 bit integer (IE results from bitwise ops, or is an integer constant fitting in that size
272(function(def_is_32_bit_integer) {
273 def_is_32_bit_integer(AST_Node, return_false);
274 def_is_32_bit_integer(AST_Number, function(_compressor) {
275 return this.value === (this.value | 0);
276 });
277 def_is_32_bit_integer(AST_UnaryPrefix, function(compressor) {
278 return this.operator == "~" ? this.expression.is_number(compressor)
279 : this.operator === "+" ? this.expression.is_32_bit_integer(compressor)
280 : false;
281 });
282 def_is_32_bit_integer(AST_Binary, function(compressor) {
283 return bitwise_binop.has(this.operator)
284 && (this.left.is_number(compressor) || this.right.is_number(compressor));
285 });
286}(function (node, func) {
287 node.DEFMETHOD("is_32_bit_integer", func);
288}));
289
290// methods to determine if an expression has a string result type
291(function(def_is_string) {
292 def_is_string(AST_Node, return_false);
293 def_is_string(AST_String, return_true);
294 def_is_string(AST_TemplateString, return_true);
295 def_is_string(AST_UnaryPrefix, function() {
296 return this.operator == "typeof";
297 });
298 def_is_string(AST_Binary, function(compressor) {
299 return this.operator == "+" &&
300 (this.left.is_string(compressor) || this.right.is_string(compressor));
301 });
302 def_is_string(AST_Assign, function(compressor) {
303 return (this.operator == "=" || this.operator == "+=") && this.right.is_string(compressor);
304 });
305 def_is_string(AST_Sequence, function(compressor) {
306 return this.tail_node().is_string(compressor);
307 });
308 def_is_string(AST_Conditional, function(compressor) {
309 return this.consequent.is_string(compressor) && this.alternative.is_string(compressor);
310 });
311})(function(node, func) {
312 node.DEFMETHOD("is_string", func);
313});
314
315export function is_undefined(node, compressor) {
316 return (
317 has_flag(node, UNDEFINED)
318 || node instanceof AST_Undefined
319 || node instanceof AST_UnaryPrefix
320 && node.operator == "void"
321 && !node.expression.has_side_effects(compressor)
322 );
323}
324
325// Is the node explicitly null or undefined.
326function is_null_or_undefined(node, compressor) {
327 let fixed;
328 return (
329 node instanceof AST_Null
330 || is_undefined(node, compressor)
331 || (
332 node instanceof AST_SymbolRef
333 && (fixed = node.definition().fixed) instanceof AST_Node
334 && is_nullish(fixed, compressor)
335 )
336 );
337}
338
339// Find out if this expression is optionally chained from a base-point that we
340// can statically analyze as null or undefined.
341export function is_nullish_shortcircuited(node, compressor) {
342 if (node instanceof AST_PropAccess || node instanceof AST_Call) {
343 return (
344 (node.optional && is_null_or_undefined(node.expression, compressor))
345 || is_nullish_shortcircuited(node.expression, compressor)
346 );
347 }
348 if (node instanceof AST_Chain) return is_nullish_shortcircuited(node.expression, compressor);
349 return false;
350}
351
352// Find out if something is == null, or can short circuit into nullish.
353// Used to optimize ?. and ??
354export function is_nullish(node, compressor) {
355 if (is_null_or_undefined(node, compressor)) return true;
356 return is_nullish_shortcircuited(node, compressor);
357}
358
359// Determine if expression might cause side effects
360// If there's a possibility that a node may change something when it's executed, this returns true
361(function(def_has_side_effects) {
362 def_has_side_effects(AST_Node, return_true);
363
364 def_has_side_effects(AST_EmptyStatement, return_false);
365 def_has_side_effects(AST_Constant, return_false);
366 def_has_side_effects(AST_This, return_false);
367
368 function any(list, compressor) {
369 for (var i = list.length; --i >= 0;)
370 if (list[i].has_side_effects(compressor))
371 return true;
372 return false;
373 }
374
375 def_has_side_effects(AST_Block, function(compressor) {
376 return any(this.body, compressor);
377 });
378 def_has_side_effects(AST_Call, function(compressor) {
379 if (
380 !this.is_callee_pure(compressor)
381 && (!this.expression.is_call_pure(compressor)
382 || this.expression.has_side_effects(compressor))
383 ) {
384 return true;
385 }
386 return any(this.args, compressor);
387 });
388 def_has_side_effects(AST_Switch, function(compressor) {
389 return this.expression.has_side_effects(compressor)
390 || any(this.body, compressor);
391 });
392 def_has_side_effects(AST_Case, function(compressor) {
393 return this.expression.has_side_effects(compressor)
394 || any(this.body, compressor);
395 });
396 def_has_side_effects(AST_Try, function(compressor) {
397 return this.body.has_side_effects(compressor)
398 || this.bcatch && this.bcatch.has_side_effects(compressor)
399 || this.bfinally && this.bfinally.has_side_effects(compressor);
400 });
401 def_has_side_effects(AST_If, function(compressor) {
402 return this.condition.has_side_effects(compressor)
403 || this.body && this.body.has_side_effects(compressor)
404 || this.alternative && this.alternative.has_side_effects(compressor);
405 });
406 def_has_side_effects(AST_ImportMeta, return_false);
407 def_has_side_effects(AST_DynamicImport, function() {
408 // `import.source(x)` only compiles the module, which is side-effect free
409 return this.phase !== "source";
410 });
411 def_has_side_effects(AST_LabeledStatement, function(compressor) {
412 return this.body.has_side_effects(compressor);
413 });
414 def_has_side_effects(AST_SimpleStatement, function(compressor) {
415 return this.body.has_side_effects(compressor);
416 });
417 def_has_side_effects(AST_Lambda, return_false);
418 def_has_side_effects(AST_Class, function (compressor) {
419 if (this.extends && this.extends.has_side_effects(compressor)) {
420 return true;
421 }
422 return any(this.properties, compressor);
423 });
424 def_has_side_effects(AST_ClassStaticBlock, function(compressor) {
425 return any(this.body, compressor);
426 });
427 def_has_side_effects(AST_Binary, function(compressor) {
428 return this.left.has_side_effects(compressor)
429 || this.right.has_side_effects(compressor);
430 });
431 def_has_side_effects(AST_Assign, return_true);
432 def_has_side_effects(AST_Conditional, function(compressor) {
433 return this.condition.has_side_effects(compressor)
434 || this.consequent.has_side_effects(compressor)
435 || this.alternative.has_side_effects(compressor);
436 });
437 def_has_side_effects(AST_Unary, function(compressor) {
438 return unary_side_effects.has(this.operator)
439 || this.expression.has_side_effects(compressor);
440 });
441 def_has_side_effects(AST_SymbolRef, function(compressor) {
442 return !this.is_declared(compressor) && !pure_prop_access_globals.has(this.name);
443 });
444 def_has_side_effects(AST_SymbolClassProperty, return_false);
445 def_has_side_effects(AST_SymbolDeclaration, return_false);
446 def_has_side_effects(AST_Object, function(compressor) {
447 return any(this.properties, compressor);
448 });
449 def_has_side_effects(AST_ObjectKeyVal, function(compressor) {
450 return (
451 this.computed_key() && this.key.has_side_effects(compressor)
452 || this.value && this.value.has_side_effects(compressor)
453 );
454 });
455 def_has_side_effects([
456 AST_ClassProperty,
457 AST_ClassPrivateProperty,
458 ], function(compressor) {
459 return (
460 this.computed_key() && this.key.has_side_effects(compressor)
461 || this.static && this.value && this.value.has_side_effects(compressor)
462 );
463 });
464 def_has_side_effects([
465 AST_PrivateMethod,
466 AST_PrivateGetter,
467 AST_PrivateSetter,
468 AST_ConciseMethod,
469 AST_ObjectGetter,
470 AST_ObjectSetter,
471 ], function(compressor) {
472 return this.computed_key() && this.key.has_side_effects(compressor);
473 });
474 def_has_side_effects(AST_Array, function(compressor) {
475 return any(this.elements, compressor);
476 });
477 def_has_side_effects(AST_Dot, function(compressor) {
478 if (is_nullish(this, compressor)) {
479 return this.expression.has_side_effects(compressor);
480 }
481 if (!this.optional && this.expression.may_throw_on_access(compressor)) {
482 return true;
483 }
484
485 return this.expression.has_side_effects(compressor);
486 });
487 def_has_side_effects(AST_Sub, function(compressor) {
488 if (is_nullish(this, compressor)) {
489 return this.expression.has_side_effects(compressor);
490 }
491 if (!this.optional && this.expression.may_throw_on_access(compressor)) {
492 return true;
493 }
494
495 var property = this.property.has_side_effects(compressor);
496 if (property && this.optional) return true; // "?." is a condition
497
498 return property || this.expression.has_side_effects(compressor);
499 });
500 def_has_side_effects(AST_Chain, function (compressor) {
501 return this.expression.has_side_effects(compressor);
502 });
503 def_has_side_effects(AST_Sequence, function(compressor) {
504 return any(this.expressions, compressor);
505 });
506 def_has_side_effects(AST_Definitions, function(compressor) {
507 return any(this.definitions, compressor);
508 });
509 def_has_side_effects(AST_VarDef, function() {
510 return this.value != null;
511 });
512 def_has_side_effects(AST_TemplateSegment, return_false);
513 def_has_side_effects(AST_TemplateString, function(compressor) {
514 return any(this.segments, compressor);
515 });
516})(function(node_or_nodes, func) {
517 for (const node of [].concat(node_or_nodes)) {
518 node.DEFMETHOD("has_side_effects", func);
519 }
520});
521
522// determine if expression may throw
523(function(def_may_throw) {
524 def_may_throw(AST_Node, return_true);
525
526 def_may_throw(AST_Constant, return_false);
527 def_may_throw(AST_EmptyStatement, return_false);
528 def_may_throw(AST_Lambda, return_false);
529 def_may_throw(AST_SymbolDeclaration, return_false);
530 def_may_throw(AST_This, return_false);
531 def_may_throw(AST_ImportMeta, return_false);
532
533 function any(list, compressor) {
534 for (var i = list.length; --i >= 0;)
535 if (list[i].may_throw(compressor))
536 return true;
537 return false;
538 }
539
540 def_may_throw(AST_Class, function(compressor) {
541 if (this.extends && this.extends.may_throw(compressor)) return true;
542 return any(this.properties, compressor);
543 });
544 def_may_throw(AST_ClassStaticBlock, function (compressor) {
545 return any(this.body, compressor);
546 });
547
548 def_may_throw(AST_Array, function(compressor) {
549 return any(this.elements, compressor);
550 });
551 def_may_throw(AST_Assign, function(compressor) {
552 if (this.right.may_throw(compressor)) return true;
553 if (!compressor.has_directive("use strict")
554 && this.operator == "="
555 && this.left instanceof AST_SymbolRef) {
556 return false;
557 }
558 return this.left.may_throw(compressor);
559 });
560 def_may_throw(AST_Binary, function(compressor) {
561 return this.left.may_throw(compressor)
562 || this.right.may_throw(compressor);
563 });
564 def_may_throw(AST_Block, function(compressor) {
565 return any(this.body, compressor);
566 });
567 def_may_throw(AST_Call, function(compressor) {
568 if (is_nullish(this, compressor)) return false;
569 if (any(this.args, compressor)) return true;
570 if (this.is_callee_pure(compressor)) return false;
571 if (this.expression.may_throw(compressor)) return true;
572 return !(this.expression instanceof AST_Lambda)
573 || any(this.expression.body, compressor);
574 });
575 def_may_throw(AST_Case, function(compressor) {
576 return this.expression.may_throw(compressor)
577 || any(this.body, compressor);
578 });
579 def_may_throw(AST_Conditional, function(compressor) {
580 return this.condition.may_throw(compressor)
581 || this.consequent.may_throw(compressor)
582 || this.alternative.may_throw(compressor);
583 });
584 def_may_throw(AST_Definitions, function(compressor) {
585 return any(this.definitions, compressor);
586 });
587 def_may_throw(AST_If, function(compressor) {
588 return this.condition.may_throw(compressor)
589 || this.body && this.body.may_throw(compressor)
590 || this.alternative && this.alternative.may_throw(compressor);
591 });
592 def_may_throw(AST_LabeledStatement, function(compressor) {
593 return this.body.may_throw(compressor);
594 });
595 def_may_throw(AST_Object, function(compressor) {
596 return any(this.properties, compressor);
597 });
598 def_may_throw(AST_ObjectKeyVal, function(compressor) {
599 return (
600 this.computed_key() && this.key.may_throw(compressor)
601 || this.value ? this.value.may_throw(compressor) : false
602 );
603 });
604 def_may_throw([
605 AST_ClassProperty,
606 AST_ClassPrivateProperty,
607 ], function(compressor) {
608 return (
609 this.computed_key() && this.key.may_throw(compressor)
610 || this.static && this.value && this.value.may_throw(compressor)
611 );
612 });
613 def_may_throw([
614 AST_ConciseMethod,
615 AST_ObjectGetter,
616 AST_ObjectSetter,
617 ], function(compressor) {
618 return this.computed_key() && this.key.may_throw(compressor);
619 });
620 def_may_throw([
621 AST_PrivateMethod,
622 AST_PrivateGetter,
623 AST_PrivateSetter,
624 ], return_false);
625 def_may_throw(AST_Return, function(compressor) {
626 return this.value && this.value.may_throw(compressor);
627 });
628 def_may_throw(AST_Sequence, function(compressor) {
629 return any(this.expressions, compressor);
630 });
631 def_may_throw(AST_SimpleStatement, function(compressor) {
632 return this.body.may_throw(compressor);
633 });
634 def_may_throw(AST_Dot, function(compressor) {
635 if (is_nullish(this, compressor)) return false;
636 return !this.optional && this.expression.may_throw_on_access(compressor)
637 || this.expression.may_throw(compressor);
638 });
639 def_may_throw(AST_Sub, function(compressor) {
640 if (is_nullish(this, compressor)) return false;
641 return !this.optional && this.expression.may_throw_on_access(compressor)
642 || this.expression.may_throw(compressor)
643 || this.property.may_throw(compressor);
644 });
645 def_may_throw(AST_Chain, function(compressor) {
646 return this.expression.may_throw(compressor);
647 });
648 def_may_throw(AST_Switch, function(compressor) {
649 return this.expression.may_throw(compressor)
650 || any(this.body, compressor);
651 });
652 def_may_throw(AST_SymbolRef, function(compressor) {
653 return !this.is_declared(compressor) && !pure_prop_access_globals.has(this.name);
654 });
655 def_may_throw(AST_SymbolClassProperty, return_false);
656 def_may_throw(AST_Try, function(compressor) {
657 return this.bcatch ? this.bcatch.may_throw(compressor) : this.body.may_throw(compressor)
658 || this.bfinally && this.bfinally.may_throw(compressor);
659 });
660 def_may_throw(AST_Unary, function(compressor) {
661 if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef)
662 return false;
663 return this.expression.may_throw(compressor);
664 });
665 def_may_throw(AST_VarDef, function(compressor) {
666 if (!this.value) return false;
667 return this.value.may_throw(compressor);
668 });
669})(function(node_or_nodes, func) {
670 for (const node of [].concat(node_or_nodes)) {
671 node.DEFMETHOD("may_throw", func);
672 }
673});
674
675// determine if expression is constant
676(function(def_is_constant_expression) {
677 function all_refs_local(scope) {
678 let result = true;
679 walk(this, node => {
680 if (node instanceof AST_SymbolRef) {
681 if (has_flag(this, INLINED)) {
682 result = false;
683 return walk_abort;
684 }
685 var def = node.definition();
686 if (
687 member(def, this.enclosed)
688 && !this.variables.has(def.name)
689 ) {
690 if (scope) {
691 var scope_def = scope.find_variable(node);
692 if (def.undeclared ? !scope_def : scope_def === def) {
693 result = "f";
694 return true;
695 }
696 }
697 result = false;
698 return walk_abort;
699 }
700 return true;
701 }
702 if (node instanceof AST_This && this instanceof AST_Arrow) {
703 result = false;
704 return walk_abort;
705 }
706 });
707 return result;
708 }
709
710 def_is_constant_expression(AST_Node, return_false);
711 def_is_constant_expression(AST_Constant, return_true);
712 def_is_constant_expression(AST_Class, function(scope) {
713 if (this.extends && !this.extends.is_constant_expression(scope)) {
714 return false;
715 }
716
717 for (const prop of this.properties) {
718 if (prop.computed_key() && !prop.key.is_constant_expression(scope)) {
719 return false;
720 }
721 if (prop.static && prop.value && !prop.value.is_constant_expression(scope)) {
722 return false;
723 }
724 if (prop instanceof AST_ClassStaticBlock) {
725 return false;
726 }
727 }
728
729 return all_refs_local.call(this, scope);
730 });
731 def_is_constant_expression(AST_Lambda, all_refs_local);
732 def_is_constant_expression(AST_Unary, function() {
733 return this.expression.is_constant_expression();
734 });
735 def_is_constant_expression(AST_Binary, function() {
736 return this.left.is_constant_expression()
737 && this.right.is_constant_expression();
738 });
739 def_is_constant_expression(AST_Array, function() {
740 return this.elements.every((l) => l.is_constant_expression());
741 });
742 def_is_constant_expression(AST_Object, function() {
743 return this.properties.every((l) => l.is_constant_expression());
744 });
745 def_is_constant_expression(AST_ObjectProperty, function() {
746 return !!(!(this.key instanceof AST_Node) && this.value && this.value.is_constant_expression());
747 });
748})(function(node, func) {
749 node.DEFMETHOD("is_constant_expression", func);
750});
751
752
753// may_throw_on_access()
754// returns true if this node may be null, undefined or contain `AST_Accessor`
755(function(def_may_throw_on_access) {
756 AST_Node.DEFMETHOD("may_throw_on_access", function(compressor) {
757 return !compressor.option("pure_getters")
758 || this._dot_throw(compressor);
759 });
760
761 function is_strict(compressor) {
762 return /strict/.test(compressor.option("pure_getters"));
763 }
764
765 def_may_throw_on_access(AST_Node, is_strict);
766 def_may_throw_on_access(AST_Null, return_true);
767 def_may_throw_on_access(AST_Undefined, return_true);
768 def_may_throw_on_access(AST_Constant, return_false);
769 def_may_throw_on_access(AST_Array, return_false);
770 def_may_throw_on_access(AST_Object, function(compressor) {
771 if (!is_strict(compressor)) return false;
772 for (var i = this.properties.length; --i >=0;)
773 if (this.properties[i]._dot_throw(compressor)) return true;
774 return false;
775 });
776 // Do not be as strict with classes as we are with objects.
777 // Hopefully the community is not going to abuse static getters and setters.
778 // https://github.com/terser/terser/issues/724#issuecomment-643655656
779 def_may_throw_on_access(AST_Class, return_false);
780 def_may_throw_on_access(AST_ObjectProperty, return_false);
781 def_may_throw_on_access(AST_ObjectGetter, return_true);
782 def_may_throw_on_access(AST_Expansion, function(compressor) {
783 return this.expression._dot_throw(compressor);
784 });
785 def_may_throw_on_access(AST_Function, return_false);
786 def_may_throw_on_access(AST_Arrow, return_false);
787 def_may_throw_on_access(AST_UnaryPostfix, return_false);
788 def_may_throw_on_access(AST_UnaryPrefix, function() {
789 return this.operator == "void";
790 });
791 def_may_throw_on_access(AST_Binary, function(compressor) {
792 return (this.operator == "&&" || this.operator == "||" || this.operator == "??")
793 && (this.left._dot_throw(compressor) || this.right._dot_throw(compressor));
794 });
795 def_may_throw_on_access(AST_Assign, function(compressor) {
796 if (this.logical) return true;
797
798 return this.operator == "="
799 && this.right._dot_throw(compressor);
800 });
801 def_may_throw_on_access(AST_Conditional, function(compressor) {
802 return this.consequent._dot_throw(compressor)
803 || this.alternative._dot_throw(compressor);
804 });
805 def_may_throw_on_access(AST_Dot, function(compressor) {
806 if (!is_strict(compressor)) return false;
807
808 if (this.property == "prototype") {
809 return !(
810 this.expression instanceof AST_Function
811 || this.expression instanceof AST_Class
812 );
813 }
814 return true;
815 });
816 def_may_throw_on_access(AST_Chain, function(compressor) {
817 return this.expression._dot_throw(compressor);
818 });
819 def_may_throw_on_access(AST_Sequence, function(compressor) {
820 return this.tail_node()._dot_throw(compressor);
821 });
822 def_may_throw_on_access(AST_SymbolRef, function(compressor) {
823 if (this.name === "arguments" && this.scope instanceof AST_Lambda) return false;
824 if (has_flag(this, UNDEFINED)) return true;
825 if (!is_strict(compressor)) return false;
826 if (is_undeclared_ref(this) && this.is_declared(compressor)) return false;
827 if (this.is_immutable()) return false;
828 var fixed = this.fixed_value();
829 return !fixed || fixed._dot_throw(compressor);
830 });
831})(function(node, func) {
832 node.DEFMETHOD("_dot_throw", func);
833});
834
835export function is_lhs(node, parent) {
836 if (parent instanceof AST_Unary && unary_side_effects.has(parent.operator)) return parent.expression;
837 if (parent instanceof AST_Assign && parent.left === node) return node;
838 if (parent instanceof AST_ForIn && parent.init === node) return node;
839}
840
841// method to negate an expression
842(function(def_negate) {
843 function basic_negation(exp) {
844 return make_node(AST_UnaryPrefix, exp, {
845 operator: "!",
846 expression: exp
847 });
848 }
849 function best(orig, alt, first_in_statement) {
850 var negated = basic_negation(orig);
851 if (first_in_statement) {
852 var stat = make_node(AST_SimpleStatement, alt, {
853 body: alt
854 });
855 return best_of_expression(negated, stat) === stat ? alt : negated;
856 }
857 return best_of_expression(negated, alt);
858 }
859 def_negate(AST_Node, function() {
860 return basic_negation(this);
861 });
862 def_negate(AST_Statement, function() {
863 throw new Error("Cannot negate a statement");
864 });
865 def_negate(AST_Function, function() {
866 return basic_negation(this);
867 });
868 def_negate(AST_Class, function() {
869 return basic_negation(this);
870 });
871 def_negate(AST_Arrow, function() {
872 return basic_negation(this);
873 });
874 def_negate(AST_UnaryPrefix, function() {
875 if (this.operator == "!")
876 return this.expression;
877 return basic_negation(this);
878 });
879 def_negate(AST_Sequence, function(compressor) {
880 var expressions = this.expressions.slice();
881 expressions.push(expressions.pop().negate(compressor));
882 return make_sequence(this, expressions);
883 });
884 def_negate(AST_Conditional, function(compressor, first_in_statement) {
885 var self = this.clone();
886 self.consequent = self.consequent.negate(compressor);
887 self.alternative = self.alternative.negate(compressor);
888 return best(this, self, first_in_statement);
889 });
890 def_negate(AST_Binary, function(compressor, first_in_statement) {
891 var self = this.clone(), op = this.operator;
892 if (compressor.option("unsafe_comps")) {
893 switch (op) {
894 case "<=" : self.operator = ">" ; return self;
895 case "<" : self.operator = ">=" ; return self;
896 case ">=" : self.operator = "<" ; return self;
897 case ">" : self.operator = "<=" ; return self;
898 }
899 }
900 switch (op) {
901 case "==" : self.operator = "!="; return self;
902 case "!=" : self.operator = "=="; return self;
903 case "===": self.operator = "!=="; return self;
904 case "!==": self.operator = "==="; return self;
905 case "&&":
906 self.operator = "||";
907 self.left = self.left.negate(compressor, first_in_statement);
908 self.right = self.right.negate(compressor);
909 return best(this, self, first_in_statement);
910 case "||":
911 self.operator = "&&";
912 self.left = self.left.negate(compressor, first_in_statement);
913 self.right = self.right.negate(compressor);
914 return best(this, self, first_in_statement);
915 }
916 return basic_negation(this);
917 });
918})(function(node, func) {
919 node.DEFMETHOD("negate", function(compressor, first_in_statement) {
920 return func.call(this, compressor, first_in_statement);
921 });
922});
923
924(function (def_bitwise_negate) {
925 function basic_bitwise_negation(exp) {
926 return make_node(AST_UnaryPrefix, exp, {
927 operator: "~",
928 expression: exp
929 });
930 }
931
932 def_bitwise_negate(AST_Node, function(_compressor) {
933 return basic_bitwise_negation(this);
934 });
935
936 def_bitwise_negate(AST_Number, function(_compressor) {
937 const neg = ~this.value;
938 if (neg.toString().length > this.value.toString().length) {
939 return basic_bitwise_negation(this);
940 }
941 return make_node(AST_Number, this, { value: neg });
942 });
943
944 def_bitwise_negate(AST_UnaryPrefix, function(compressor, in_32_bit_context) {
945 if (
946 this.operator == "~"
947 && (
948 this.expression.is_32_bit_integer(compressor) ||
949 (in_32_bit_context != null ? in_32_bit_context : compressor.in_32_bit_context())
950 )
951 ) {
952 return this.expression;
953 } else {
954 return basic_bitwise_negation(this);
955 }
956 });
957})(function (node, func) {
958 node.DEFMETHOD("bitwise_negate", func);
959});
960
961// Is the callee of this function pure?
962var global_pure_fns = makePredicate("Boolean decodeURI decodeURIComponent Date encodeURI encodeURIComponent Error escape EvalError isFinite isNaN Number Object parseFloat parseInt RangeError ReferenceError String SyntaxError TypeError unescape URIError");
963AST_Call.DEFMETHOD("is_callee_pure", function(compressor) {
964 if (compressor.option("unsafe")) {
965 var expr = this.expression;
966 var first_arg;
967 if (
968 expr.expression && expr.expression.name === "hasOwnProperty" &&
969 (
970 (first_arg = (this.args && this.args[0] && this.args[0].evaluate(compressor))) == null
971 || first_arg.thedef && first_arg.thedef.undeclared
972 )
973 ) {
974 return false;
975 }
976 if (is_undeclared_ref(expr) && global_pure_fns.has(expr.name)) return true;
977 if (is_pure_builtin_call(compressor, this)) return true;
978 } else if (compressor.option("builtins_pure")) {
979 if (is_pure_builtin_call(compressor, this)) return true;
980 }
981 if ((this instanceof AST_New) && compressor.option("pure_new")) {
982 return true;
983 }
984 if (compressor.option("side_effects") && has_annotation(this, _PURE)) {
985 return true;
986 }
987 return !compressor.pure_funcs(this);
988});
989
990// If I call this, is it a pure function?
991AST_Node.DEFMETHOD("is_call_pure", return_false);
992AST_Dot.DEFMETHOD("is_call_pure", function(compressor) {
993 if (!compressor.option("unsafe")) return;
994 const expr = this.expression;
995
996 let native_obj;
997 if (expr instanceof AST_Array) {
998 native_obj = "Array";
999 } else if (expr.is_boolean()) {
1000 native_obj = "Boolean";
1001 } else if (expr.is_number(compressor)) {
1002 native_obj = "Number";
1003 } else if (expr instanceof AST_RegExp) {
1004 native_obj = "RegExp";
1005 } else if (expr.is_string(compressor)) {
1006 native_obj = "String";
1007 } else if (!this.may_throw_on_access(compressor)) {
1008 native_obj = "Object";
1009 }
1010 return native_obj != null && compressor.is_pure_native_method(native_obj, this.property);
1011});
1012
1013// tell me if a statement aborts
1014export const aborts = (thing) => thing && thing.aborts();
1015
1016(function(def_aborts) {
1017 def_aborts(AST_Statement, return_null);
1018 def_aborts(AST_Jump, return_this);
1019 function block_aborts() {
1020 for (var i = 0; i < this.body.length; i++) {
1021 if (aborts(this.body[i])) {
1022 return this.body[i];
1023 }
1024 }
1025 return null;
1026 }
1027 def_aborts(AST_Import, return_null);
1028 def_aborts(AST_BlockStatement, block_aborts);
1029 def_aborts(AST_SwitchBranch, block_aborts);
1030 def_aborts(AST_DefClass, function () {
1031 for (const prop of this.properties) {
1032 if (prop instanceof AST_ClassStaticBlock) {
1033 if (prop.aborts()) return prop;
1034 }
1035 }
1036 return null;
1037 });
1038 def_aborts(AST_ClassStaticBlock, block_aborts);
1039 def_aborts(AST_If, function() {
1040 return this.alternative && aborts(this.body) && aborts(this.alternative) && this;
1041 });
1042})(function(node, func) {
1043 node.DEFMETHOD("aborts", func);
1044});
1045
1046AST_Node.DEFMETHOD("contains_this", function() {
1047 return walk(this, node => {
1048 if (node instanceof AST_This) return walk_abort;
1049 if (
1050 node !== this
1051 && node instanceof AST_Scope
1052 && !(node instanceof AST_Arrow)
1053 ) {
1054 return true;
1055 }
1056 });
1057});
1058
1059export function is_modified(compressor, tw, node, value, level, immutable) {
1060 var parent = tw.parent(level);
1061 var lhs = is_lhs(node, parent);
1062 if (lhs) return lhs;
1063 if (!immutable
1064 && parent instanceof AST_Call
1065 && parent.expression === node
1066 && !(value instanceof AST_Arrow)
1067 && !(value instanceof AST_Class)
1068 && !parent.is_callee_pure(compressor)
1069 && (!(value instanceof AST_Function)
1070 || !(parent instanceof AST_New) && value.contains_this())) {
1071 return true;
1072 }
1073 if (parent instanceof AST_Array) {
1074 return is_modified(compressor, tw, parent, parent, level + 1);
1075 }
1076 if (parent instanceof AST_ObjectKeyVal && node === parent.value) {
1077 var obj = tw.parent(level + 1);
1078 return is_modified(compressor, tw, obj, obj, level + 2);
1079 }
1080 if (parent instanceof AST_PropAccess && parent.expression === node) {
1081 var prop = read_property(value, parent.property);
1082 return !immutable && is_modified(compressor, tw, parent, prop, level + 1);
1083 }
1084}
1085
1086/**
1087 * Check if a node may be used by the expression it's in
1088 * void (0, 1, {node}, 2) -> false
1089 * console.log(0, {node}) -> true
1090 */
1091export function is_used_in_expression(tw) {
1092 for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
1093 if (parent instanceof AST_Sequence) {
1094 const nth_expression = parent.expressions.indexOf(node);
1095 if (nth_expression !== parent.expressions.length - 1) {
1096 // Detect (0, x.noThis)() constructs
1097 const grandparent = tw.parent(p + 2);
1098 if (
1099 parent.expressions.length > 2
1100 || parent.expressions.length === 1
1101 || !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
1102 ) {
1103 return false;
1104 }
1105 return true;
1106 } else {
1107 continue;
1108 }
1109 }
1110 if (parent instanceof AST_Unary) {
1111 const op = parent.operator;
1112 if (op === "void") {
1113 return false;
1114 }
1115 if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
1116 continue;
1117 }
1118 }
1119 if (
1120 parent instanceof AST_SimpleStatement
1121 || parent instanceof AST_LabeledStatement
1122 ) {
1123 return false;
1124 }
1125 if (parent instanceof AST_Scope) {
1126 return false;
1127 }
1128 return true;
1129 }
1130
1131 return true;
1132}
Note: See TracBrowser for help on using the repository browser.