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