source: frontend/node_modules/terser/lib/compress/inline.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: 23.0 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_Assign,
47 AST_Block,
48 AST_Call,
49 AST_Catch,
50 AST_Class,
51 AST_ClassExpression,
52 AST_DefaultAssign,
53 AST_DefClass,
54 AST_Defun,
55 AST_Destructuring,
56 AST_EmptyStatement,
57 AST_Expansion,
58 AST_Export,
59 AST_Function,
60 AST_IterationStatement,
61 AST_Lambda,
62 AST_Node,
63 AST_Number,
64 AST_Object,
65 AST_ObjectKeyVal,
66 AST_PropAccess,
67 AST_Return,
68 AST_Scope,
69 AST_SimpleStatement,
70 AST_Statement,
71 AST_SymbolDefun,
72 AST_SymbolFunarg,
73 AST_SymbolLambda,
74 AST_SymbolRef,
75 AST_SymbolVar,
76 AST_This,
77 AST_Toplevel,
78 AST_UnaryPrefix,
79 AST_Var,
80 AST_VarDef,
81
82 walk,
83
84 _INLINE,
85 _NOINLINE,
86 _PURE,
87} from "../ast.js";
88import { make_node, make_void_0, has_annotation } from "../utils/index.js";
89import "../size.js";
90
91import "./evaluate.js";
92import "./drop-side-effect-free.js";
93import "./reduce-vars.js";
94import {
95 SQUEEZED,
96 INLINED,
97 UNUSED,
98
99 has_flag,
100 set_flag,
101} from "./compressor-flags.js";
102import {
103 make_sequence,
104 best_of,
105 make_node_from_constant,
106 identifier_atom,
107 is_empty,
108 is_func_expr,
109 is_iife_call,
110 is_reachable,
111 is_recursive_ref,
112 retain_top_func,
113} from "./common.js";
114
115/**
116 * Module that contains the inlining logic.
117 *
118 * @module
119 *
120 * The stars of the show are `inline_into_symbolref` and `inline_into_call`.
121 */
122
123function within_array_or_object_literal(compressor) {
124 var node, level = 0;
125 while (node = compressor.parent(level++)) {
126 if (node instanceof AST_Statement) return false;
127 if (node instanceof AST_Array
128 || node instanceof AST_ObjectKeyVal
129 || node instanceof AST_Object) {
130 return true;
131 }
132 }
133 return false;
134}
135
136function scope_encloses_variables_in_this_scope(scope, pulled_scope) {
137 for (const enclosed of pulled_scope.enclosed) {
138 if (pulled_scope.variables.has(enclosed.name)) {
139 continue;
140 }
141 const looked_up = scope.find_variable(enclosed.name);
142 if (looked_up) {
143 if (looked_up === enclosed) continue;
144 return true;
145 }
146 }
147 return false;
148}
149
150/**
151 * An extra check function for `top_retain` option, compare the length of const identifier
152 * and init value length and return true if init value is longer than identifier. for example:
153 * ```
154 * // top_retain: ["example"]
155 * const example = 100
156 * ```
157 * it will return false because length of "100" is short than identifier "example".
158 */
159function is_const_symbol_short_than_init_value(def, fixed_value) {
160 if (def.orig.length === 1 && fixed_value) {
161 const init_value_length = fixed_value.size();
162 const identifer_length = def.name.length;
163 return init_value_length > identifer_length;
164 }
165 return true;
166}
167
168export function inline_into_symbolref(self, compressor) {
169 if (compressor.in_computed_key()) return self;
170
171 const parent = compressor.parent();
172 const def = self.definition();
173 const nearest_scope = compressor.find_scope();
174 let fixed = self.fixed_value();
175 if (
176 compressor.top_retain &&
177 def.global &&
178 compressor.top_retain(def) &&
179 // when identifier is in top_retain option dose not mean we can always inline it.
180 // if identifier name is longer then init value, we can replace it.
181 is_const_symbol_short_than_init_value(def, fixed)
182 ) {
183 // keep it
184 def.fixed = false;
185 def.single_use = false;
186 return self;
187 }
188
189 if (dont_inline_lambda_in_loop(compressor, fixed)) return self;
190
191 let single_use = def.single_use
192 && !(parent instanceof AST_Call
193 && (parent.is_callee_pure(compressor))
194 || has_annotation(parent, _NOINLINE))
195 && !(parent instanceof AST_Export
196 && fixed instanceof AST_Lambda
197 && fixed.name);
198
199 if (single_use && fixed instanceof AST_Node) {
200 single_use =
201 !fixed.has_side_effects(compressor)
202 && !fixed.may_throw(compressor);
203 }
204
205 if (fixed instanceof AST_Class && def.scope !== self.scope) {
206 return self;
207 }
208
209 if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) {
210 if (retain_top_func(fixed, compressor)) {
211 single_use = false;
212 } else if (def.scope !== self.scope
213 && (def.escaped == 1
214 || has_flag(fixed, INLINED)
215 || within_array_or_object_literal(compressor)
216 || !compressor.option("reduce_funcs"))) {
217 single_use = false;
218 } else if (is_recursive_ref(compressor, def)) {
219 single_use = false;
220 } else if (def.scope !== self.scope || def.orig[0] instanceof AST_SymbolFunarg) {
221 single_use = fixed.is_constant_expression(self.scope);
222 if (single_use == "f") {
223 var scope = self.scope;
224 do {
225 if (scope instanceof AST_Defun || is_func_expr(scope)) {
226 set_flag(scope, INLINED);
227 }
228 } while (scope = scope.parent_scope);
229 }
230 }
231 }
232
233 if (single_use && (fixed instanceof AST_Lambda || fixed instanceof AST_Class)) {
234 single_use =
235 def.scope === self.scope
236 && !scope_encloses_variables_in_this_scope(nearest_scope, fixed)
237 || parent instanceof AST_Call
238 && parent.expression === self
239 && !scope_encloses_variables_in_this_scope(nearest_scope, fixed)
240 && !(fixed.name && fixed.name.definition().recursive_refs > 0);
241 }
242
243 if (single_use && fixed) {
244 if (fixed instanceof AST_DefClass) {
245 set_flag(fixed, SQUEEZED);
246 fixed = make_node(AST_ClassExpression, fixed, fixed);
247 }
248 if (fixed instanceof AST_Defun) {
249 set_flag(fixed, SQUEEZED);
250 fixed = make_node(AST_Function, fixed, fixed);
251 }
252 if (def.recursive_refs > 0 && fixed.name instanceof AST_SymbolDefun) {
253 const defun_def = fixed.name.definition();
254 let lambda_def = fixed.variables.get(fixed.name.name);
255 let name = lambda_def && lambda_def.orig[0];
256 if (!(name instanceof AST_SymbolLambda)) {
257 name = make_node(AST_SymbolLambda, fixed.name, fixed.name);
258 name.scope = fixed;
259 fixed.name = name;
260 lambda_def = fixed.def_function(name);
261 }
262 walk(fixed, node => {
263 if (node instanceof AST_SymbolRef && node.definition() === defun_def) {
264 node.thedef = lambda_def;
265 lambda_def.references.push(node);
266 }
267 });
268 }
269 if (
270 (fixed instanceof AST_Lambda || fixed instanceof AST_Class)
271 && fixed.parent_scope !== nearest_scope
272 ) {
273 fixed = fixed.clone(true, compressor.get_toplevel());
274
275 nearest_scope.add_child_scope(fixed);
276 }
277 return fixed.optimize(compressor);
278 }
279
280 // multiple uses
281 if (fixed) {
282 let replace;
283
284 if (fixed instanceof AST_This) {
285 if (!(def.orig[0] instanceof AST_SymbolFunarg)
286 && def.references.every((ref) =>
287 def.scope === ref.scope
288 )) {
289 replace = fixed;
290 }
291 } else {
292 var ev = fixed.evaluate(compressor);
293 if (
294 ev !== fixed
295 && (compressor.option("unsafe_regexp") || !(ev instanceof RegExp))
296 ) {
297 replace = make_node_from_constant(ev, fixed);
298 }
299 }
300
301 if (replace) {
302 const name_length = self.size(compressor);
303 const replace_size = replace.size(compressor);
304
305 let overhead = 0;
306 if (compressor.option("unused") && !compressor.exposed(def)) {
307 overhead =
308 (name_length + 2 + fixed.size(compressor)) /
309 (def.references.length - def.assignments);
310 }
311
312 if (replace_size <= name_length + overhead) {
313 return replace;
314 }
315 }
316 }
317
318 return self;
319}
320
321export function inline_into_call(self, compressor) {
322 if (compressor.in_computed_key()) return self;
323
324 var exp = self.expression;
325 var fn = exp;
326 var simple_args = self.args.every((arg) => !(arg instanceof AST_Expansion));
327
328 if (compressor.option("reduce_vars")
329 && fn instanceof AST_SymbolRef
330 && !has_annotation(self, _NOINLINE)
331 ) {
332 const fixed = fn.fixed_value();
333
334 if (
335 retain_top_func(fixed, compressor)
336 || !compressor.toplevel.funcs && exp.definition().global
337 ) {
338 return self;
339 }
340
341 fn = fixed;
342 }
343
344 if (
345 dont_inline_lambda_in_loop(compressor, fn)
346 && !has_annotation(self, _INLINE)
347 ) return self;
348
349 var is_func = fn instanceof AST_Lambda;
350
351 var stat = is_func && fn.body[0];
352 var is_regular_func = is_func && !fn.is_generator && !fn.async;
353 var can_inline = is_regular_func && compressor.option("inline") && !self.is_callee_pure(compressor);
354 if (can_inline && stat instanceof AST_Return) {
355 let returned = stat.value;
356 if (!returned || returned.is_constant_expression()) {
357 if (returned) {
358 returned = returned.clone(true);
359 } else {
360 returned = make_void_0(self);
361 }
362 const args = self.args.concat(returned);
363 return make_sequence(self, args).optimize(compressor);
364 }
365
366 // optimize identity function
367 if (
368 fn.argnames.length === 1
369 && (fn.argnames[0] instanceof AST_SymbolFunarg)
370 && self.args.length < 2
371 && !(self.args[0] instanceof AST_Expansion)
372 && returned instanceof AST_SymbolRef
373 && returned.name === fn.argnames[0].name
374 ) {
375 const replacement =
376 (self.args[0] || make_void_0()).optimize(compressor);
377
378 let parent;
379 if (
380 replacement instanceof AST_PropAccess
381 && (parent = compressor.parent()) instanceof AST_Call
382 && parent.expression === self
383 ) {
384 // identity function was being used to remove `this`, like in
385 //
386 // id(bag.no_this)(...)
387 //
388 // Replace with a larger but more effish (0, bag.no_this) wrapper.
389
390 return make_sequence(self, [
391 make_node(AST_Number, self, { value: 0 }),
392 replacement
393 ]);
394 }
395 // replace call with first argument or undefined if none passed
396 return replacement;
397 }
398 }
399
400 if (can_inline) {
401 var scope, in_loop, level = -1;
402 let def;
403 let returned_value;
404 let nearest_scope;
405 if (simple_args
406 && !fn.uses_arguments
407 && !(compressor.parent() instanceof AST_Class)
408 && !(fn.name && fn instanceof AST_Function)
409 && (returned_value = can_flatten_body(stat))
410 && (exp === fn
411 || has_annotation(self, _INLINE)
412 || compressor.option("unused")
413 && (def = exp.definition()).references.length == 1
414 && !is_recursive_ref(compressor, def)
415 && fn.is_constant_expression(exp.scope))
416 && !has_annotation(self, _PURE | _NOINLINE)
417 && !fn.contains_this()
418 && can_inject_symbols()
419 && (nearest_scope = compressor.find_scope())
420 && !scope_encloses_variables_in_this_scope(nearest_scope, fn)
421 && !(function in_default_assign() {
422 // Due to the fact function parameters have their own scope
423 // which can't use `var something` in the function body within,
424 // we simply don't inline into DefaultAssign.
425 let i = 0;
426 let p;
427 while ((p = compressor.parent(i++))) {
428 if (p instanceof AST_DefaultAssign) return true;
429 if (p instanceof AST_Block) break;
430 }
431 return false;
432 })()
433 && !(scope instanceof AST_Class)
434 ) {
435 set_flag(fn, SQUEEZED);
436 nearest_scope.add_child_scope(fn);
437 return make_sequence(self, flatten_fn(returned_value)).optimize(compressor);
438 }
439 }
440
441 if (can_inline && has_annotation(self, _INLINE)) {
442 set_flag(fn, SQUEEZED);
443 fn = make_node(fn.CTOR === AST_Defun ? AST_Function : fn.CTOR, fn, fn);
444 fn = fn.clone(true);
445 fn.figure_out_scope({}, {
446 parent_scope: compressor.find_scope(),
447 toplevel: compressor.get_toplevel()
448 });
449
450 return make_node(AST_Call, self, {
451 expression: fn,
452 args: self.args,
453 }).optimize(compressor);
454 }
455
456 const can_drop_this_call = is_regular_func && compressor.option("side_effects") && fn.body.every(is_empty);
457 if (can_drop_this_call) {
458 var args = self.args.concat(make_void_0(self));
459 return make_sequence(self, args).optimize(compressor);
460 }
461
462 if (compressor.option("negate_iife")
463 && compressor.parent() instanceof AST_SimpleStatement
464 && is_iife_call(self)) {
465 return self.negate(compressor, true);
466 }
467
468 var ev = self.evaluate(compressor);
469 if (ev !== self) {
470 ev = make_node_from_constant(ev, self).optimize(compressor);
471 return best_of(compressor, ev, self);
472 }
473
474 return self;
475
476 function return_value(stat) {
477 if (!stat) return make_void_0(self);
478 if (stat instanceof AST_Return) {
479 if (!stat.value) return make_void_0(self);
480 return stat.value.clone(true);
481 }
482 if (stat instanceof AST_SimpleStatement) {
483 return make_node(AST_UnaryPrefix, stat, {
484 operator: "void",
485 expression: stat.body.clone(true)
486 });
487 }
488 }
489
490 function can_flatten_body(stat) {
491 var body = fn.body;
492 var len = body.length;
493 if (compressor.option("inline") < 3) {
494 return len == 1 && return_value(stat);
495 }
496 stat = null;
497 for (var i = 0; i < len; i++) {
498 var line = body[i];
499 if (line instanceof AST_Var) {
500 if (stat && !line.definitions.every((var_def) =>
501 !var_def.value
502 )) {
503 return false;
504 }
505 } else if (stat) {
506 return false;
507 } else if (!(line instanceof AST_EmptyStatement)) {
508 stat = line;
509 }
510 }
511 return return_value(stat);
512 }
513
514 function can_inject_args(block_scoped, safe_to_inject) {
515 for (var i = 0, len = fn.argnames.length; i < len; i++) {
516 var arg = fn.argnames[i];
517 if (arg instanceof AST_DefaultAssign) {
518 if (has_flag(arg.left, UNUSED)) continue;
519 return false;
520 }
521 if (arg instanceof AST_Destructuring) return false;
522 if (arg instanceof AST_Expansion) {
523 if (has_flag(arg.expression, UNUSED)) continue;
524 return false;
525 }
526 if (has_flag(arg, UNUSED)) continue;
527 if (!safe_to_inject
528 || block_scoped.has(arg.name)
529 || identifier_atom.has(arg.name)
530 || scope.conflicting_def(arg.name)) {
531 return false;
532 }
533 if (in_loop) in_loop.push(arg.definition());
534 }
535 return true;
536 }
537
538 function can_inject_vars(block_scoped, safe_to_inject) {
539 var len = fn.body.length;
540 for (var i = 0; i < len; i++) {
541 var stat = fn.body[i];
542 if (!(stat instanceof AST_Var)) continue;
543 if (!safe_to_inject) return false;
544 for (var j = stat.definitions.length; --j >= 0;) {
545 var name = stat.definitions[j].name;
546 if (name instanceof AST_Destructuring
547 || block_scoped.has(name.name)
548 || identifier_atom.has(name.name)
549 || scope.conflicting_def(name.name)) {
550 return false;
551 }
552 if (in_loop) in_loop.push(name.definition());
553 }
554 }
555 return true;
556 }
557
558 function can_inject_symbols() {
559 var block_scoped = new Set();
560 do {
561 scope = compressor.parent(++level);
562 if (scope.is_block_scope() && scope.block_scope) {
563 // TODO this is sometimes undefined during compression.
564 // But it should always have a value!
565 scope.block_scope.variables.forEach(function (variable) {
566 block_scoped.add(variable.name);
567 });
568 }
569 if (scope instanceof AST_Catch) {
570 // TODO can we delete? AST_Catch is a block scope.
571 if (scope.argname) {
572 block_scoped.add(scope.argname.name);
573 }
574 } else if (scope instanceof AST_IterationStatement) {
575 in_loop = [];
576 } else if (scope instanceof AST_SymbolRef) {
577 if (scope.fixed_value() instanceof AST_Scope) return false;
578 }
579 } while (!(scope instanceof AST_Scope));
580
581 var safe_to_inject = !(scope instanceof AST_Toplevel) || compressor.toplevel.vars;
582 var inline = compressor.option("inline");
583 if (!can_inject_vars(block_scoped, inline >= 3 && safe_to_inject)) return false;
584 if (!can_inject_args(block_scoped, inline >= 2 && safe_to_inject)) return false;
585 return !in_loop || in_loop.length == 0 || !is_reachable(fn, in_loop);
586 }
587
588 function append_var(decls, expressions, name, value) {
589 var def = name.definition();
590
591 // Name already exists, only when a function argument had the same name
592 const already_appended = scope.variables.has(name.name);
593 if (!already_appended) {
594 scope.variables.set(name.name, def);
595 scope.enclosed.push(def);
596 decls.push(make_node(AST_VarDef, name, {
597 name: name,
598 value: null
599 }));
600 }
601
602 var sym = make_node(AST_SymbolRef, name, name);
603 def.references.push(sym);
604 if (value) expressions.push(make_node(AST_Assign, self, {
605 operator: "=",
606 logical: false,
607 left: sym,
608 right: value.clone()
609 }));
610 }
611
612 function flatten_args(decls, expressions) {
613 var len = fn.argnames.length;
614 for (var i = self.args.length; --i >= len;) {
615 expressions.push(self.args[i]);
616 }
617 for (i = len; --i >= 0;) {
618 var name = fn.argnames[i];
619 var value = self.args[i];
620 if (has_flag(name, UNUSED) || !name.name || scope.conflicting_def(name.name)) {
621 if (value) expressions.push(value);
622 } else {
623 var symbol = make_node(AST_SymbolVar, name, name);
624 name.definition().orig.push(symbol);
625 if (!value && in_loop) value = make_void_0(self);
626 append_var(decls, expressions, symbol, value);
627 }
628 }
629 decls.reverse();
630 expressions.reverse();
631 }
632
633 function flatten_vars(decls, expressions) {
634 var pos = expressions.length;
635 for (var i = 0, lines = fn.body.length; i < lines; i++) {
636 var stat = fn.body[i];
637 if (!(stat instanceof AST_Var)) continue;
638 for (var j = 0, defs = stat.definitions.length; j < defs; j++) {
639 var var_def = stat.definitions[j];
640 var name = var_def.name;
641 append_var(decls, expressions, name, var_def.value);
642 if (in_loop && fn.argnames.every((argname) =>
643 argname.name != name.name
644 )) {
645 var def = fn.variables.get(name.name);
646 var sym = make_node(AST_SymbolRef, name, name);
647 def.references.push(sym);
648 expressions.splice(pos++, 0, make_node(AST_Assign, var_def, {
649 operator: "=",
650 logical: false,
651 left: sym,
652 right: make_void_0(name),
653 }));
654 }
655 }
656 }
657 }
658
659 function flatten_fn(returned_value) {
660 var decls = [];
661 var expressions = [];
662 flatten_args(decls, expressions);
663 flatten_vars(decls, expressions);
664 expressions.push(returned_value);
665
666 if (decls.length) {
667 const i = scope.body.indexOf(compressor.parent(level - 1)) + 1;
668 scope.body.splice(i, 0, make_node(AST_Var, fn, {
669 definitions: decls
670 }));
671 }
672
673 return expressions.map(exp => exp.clone(true));
674 }
675}
676
677/** prevent inlining functions into loops, for performance reasons */
678function dont_inline_lambda_in_loop(compressor, maybe_lambda) {
679 return (
680 (maybe_lambda instanceof AST_Lambda || maybe_lambda instanceof AST_Class)
681 && !!compressor.is_within_loop()
682 );
683}
Note: See TracBrowser for help on using the repository browser.