source: imaps-frontend/node_modules/terser/lib/compress/inline.js

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

F4 Finalna Verzija

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