source: frontend/node_modules/terser/lib/scope.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: 34.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
44"use strict";
45
46import {
47 defaults,
48 keep_name,
49 mergeSort,
50 push_uniq,
51 make_node,
52 return_false,
53 return_this,
54 return_true,
55 string_template,
56} from "./utils/index.js";
57import {
58 AST_Arrow,
59 AST_Block,
60 AST_Call,
61 AST_Class,
62 AST_Conditional,
63 AST_DefClass,
64 AST_Defun,
65 AST_Destructuring,
66 AST_Dot,
67 AST_DotHash,
68 AST_Export,
69 AST_For,
70 AST_ForIn,
71 AST_ForOf,
72 AST_Function,
73 AST_Import,
74 AST_IterationStatement,
75 AST_Label,
76 AST_LabeledStatement,
77 AST_LabelRef,
78 AST_Lambda,
79 AST_LoopControl,
80 AST_NameMapping,
81 AST_Node,
82 AST_Scope,
83 AST_Sequence,
84 AST_String,
85 AST_Sub,
86 AST_Switch,
87 AST_SwitchBranch,
88 AST_Symbol,
89 AST_SymbolBlockDeclaration,
90 AST_SymbolCatch,
91 AST_SymbolClass,
92 AST_SymbolConst,
93 AST_SymbolDefClass,
94 AST_SymbolDefun,
95 AST_SymbolExport,
96 AST_SymbolFunarg,
97 AST_SymbolImport,
98 AST_SymbolLambda,
99 AST_SymbolLet,
100 AST_SymbolMethod,
101 AST_SymbolRef,
102 AST_SymbolUsing,
103 AST_SymbolVar,
104 AST_Toplevel,
105 AST_VarDef,
106 AST_With,
107 TreeWalker,
108 walk,
109 walk_abort
110} from "./ast.js";
111import {
112 ALL_RESERVED_WORDS,
113 js_error,
114} from "./parse.js";
115
116const MASK_EXPORT_DONT_MANGLE = 1 << 0;
117const MASK_EXPORT_WANT_MANGLE = 1 << 1;
118
119let function_defs = null;
120let unmangleable_names = null;
121/**
122 * When defined, there is a function declaration somewhere that's inside of a block.
123 * See https://tc39.es/ecma262/multipage/additional-ecmascript-features-for-web-browsers.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics
124*/
125let scopes_with_block_defuns = null;
126
127class SymbolDef {
128 constructor(scope, orig, init) {
129 this.name = orig.name;
130 this.orig = [ orig ];
131 this.init = init;
132 this.eliminated = 0;
133 this.assignments = 0;
134 this.scope = scope;
135 this.replaced = 0;
136 this.global = false;
137 this.export = 0;
138 this.mangled_name = null;
139 this.undeclared = false;
140 this.id = SymbolDef.next_id++;
141 this.chained = false;
142 this.direct_access = false;
143 this.escaped = 0;
144 this.recursive_refs = 0;
145 this.references = [];
146 this.should_replace = undefined;
147 this.single_use = false;
148 this.fixed = false;
149 Object.seal(this);
150 }
151 fixed_value() {
152 if (!this.fixed || this.fixed instanceof AST_Node) return this.fixed;
153 return this.fixed();
154 }
155 unmangleable(options) {
156 if (!options) options = {};
157
158 if (
159 function_defs &&
160 function_defs.has(this.id) &&
161 keep_name(options.keep_fnames, this.orig[0].name)
162 ) return true;
163
164 return this.global && !options.toplevel
165 || (this.export & MASK_EXPORT_DONT_MANGLE)
166 || this.undeclared
167 || !options.eval && this.scope.pinned()
168 || (this.orig[0] instanceof AST_SymbolLambda
169 || this.orig[0] instanceof AST_SymbolDefun) && keep_name(options.keep_fnames, this.orig[0].name)
170 || this.orig[0] instanceof AST_SymbolMethod
171 || (this.orig[0] instanceof AST_SymbolClass
172 || this.orig[0] instanceof AST_SymbolDefClass) && keep_name(options.keep_classnames, this.orig[0].name);
173 }
174 mangle(options) {
175 const cache = options.cache && options.cache.props;
176 if (this.global && cache && cache.has(this.name)) {
177 this.mangled_name = cache.get(this.name);
178 } else if (!this.mangled_name && !this.unmangleable(options)) {
179 var s = this.scope;
180 var sym = this.orig[0];
181 if (options.ie8 && sym instanceof AST_SymbolLambda)
182 s = s.parent_scope;
183 const redefinition = redefined_catch_def(this);
184 this.mangled_name = redefinition
185 ? redefinition.mangled_name || redefinition.name
186 : s.next_mangled(options, this);
187 if (this.global && cache) {
188 cache.set(this.name, this.mangled_name);
189 }
190 }
191 }
192}
193
194SymbolDef.next_id = 1;
195
196function redefined_catch_def(def) {
197 if (def.orig[0] instanceof AST_SymbolCatch
198 && def.scope.is_block_scope()
199 ) {
200 return def.scope.get_defun_scope().variables.get(def.name);
201 }
202}
203
204AST_Scope.DEFMETHOD("figure_out_scope", function(options, { parent_scope = undefined, toplevel = this } = {}) {
205 options = defaults(options, {
206 cache: null,
207 ie8: false,
208 safari10: false,
209 module: false,
210 });
211
212 if (!(toplevel instanceof AST_Toplevel)) {
213 throw new Error("Invalid toplevel scope");
214 }
215
216 // pass 1: setup scope chaining and handle definitions
217 var scope = this.parent_scope = parent_scope;
218 var labels = new Map();
219 var defun = null;
220 var in_destructuring = null;
221 var for_scopes = [];
222 var tw = new TreeWalker((node, descend) => {
223 if (node.is_block_scope()) {
224 const save_scope = scope;
225 node.block_scope = scope = new AST_Scope(node);
226 scope._block_scope = true;
227 scope.init_scope_vars(save_scope);
228 scope.uses_with = save_scope.uses_with;
229 scope.uses_eval = save_scope.uses_eval;
230
231 if (options.safari10) {
232 if (node instanceof AST_For || node instanceof AST_ForIn || node instanceof AST_ForOf) {
233 for_scopes.push(scope);
234 }
235 }
236
237 if (node instanceof AST_Switch) {
238 // XXX: HACK! Ensure the switch expression gets the correct scope (the parent scope) and the body gets the contained scope
239 // AST_Switch has a scope within the body, but it itself "is a block scope"
240 // This means the switched expression has to belong to the outer scope
241 // while the body inside belongs to the switch itself.
242 // This is pretty nasty and warrants an AST change
243 const the_block_scope = scope;
244 scope = save_scope;
245 node.expression.walk(tw);
246 scope = the_block_scope;
247 for (let i = 0; i < node.body.length; i++) {
248 node.body[i].walk(tw);
249 }
250 } else {
251 descend();
252 }
253 scope = save_scope;
254 return true;
255 }
256 if (node instanceof AST_Destructuring) {
257 const save_destructuring = in_destructuring;
258 in_destructuring = node;
259 descend();
260 in_destructuring = save_destructuring;
261 return true;
262 }
263 if (node instanceof AST_Scope) {
264 node.init_scope_vars(scope);
265 var save_scope = scope;
266 var save_defun = defun;
267 var save_labels = labels;
268 defun = scope = node;
269 labels = new Map();
270 descend();
271 scope = save_scope;
272 defun = save_defun;
273 labels = save_labels;
274 return true; // don't descend again in TreeWalker
275 }
276 if (node instanceof AST_LabeledStatement) {
277 var l = node.label;
278 if (labels.has(l.name)) {
279 throw new Error(string_template("Label {name} defined twice", l));
280 }
281 labels.set(l.name, l);
282 descend();
283 labels.delete(l.name);
284 return true; // no descend again
285 }
286 if (node instanceof AST_With) {
287 for (var s = scope; s; s = s.parent_scope)
288 s.uses_with = true;
289 return;
290 }
291 if (node instanceof AST_Symbol) {
292 node.scope = scope;
293 }
294 if (node instanceof AST_Label) {
295 node.thedef = node;
296 node.references = [];
297 }
298 if (node instanceof AST_SymbolLambda) {
299 defun.def_function(node, node.name == "arguments" ? undefined : defun);
300 } else if (node instanceof AST_SymbolDefun) {
301 // Careful here, the scope where this should be defined is
302 // the parent scope. The reason is that we enter a new
303 // scope when we encounter the AST_Defun node (which is
304 // instanceof AST_Scope) but we get to the symbol a bit
305 // later.
306 const closest_scope = defun.parent_scope;
307
308 // In strict mode, function definitions are block-scoped
309 node.scope = tw.directives["use strict"]
310 ? closest_scope
311 : closest_scope.get_defun_scope();
312
313 mark_export(node.scope.def_function(node, defun), 1);
314 } else if (node instanceof AST_SymbolClass) {
315 mark_export(defun.def_variable(node, defun), 1);
316 } else if (node instanceof AST_SymbolImport) {
317 scope.def_variable(node);
318 } else if (node instanceof AST_SymbolDefClass) {
319 // This deals with the name of the class being available
320 // inside the class.
321 mark_export((node.scope = defun.parent_scope).def_function(node, defun), 1);
322 } else if (
323 node instanceof AST_SymbolVar
324 || node instanceof AST_SymbolLet
325 || node instanceof AST_SymbolConst
326 || node instanceof AST_SymbolUsing
327 || node instanceof AST_SymbolCatch
328 ) {
329 var def;
330 if (node instanceof AST_SymbolBlockDeclaration) {
331 def = scope.def_variable(node, null);
332 } else {
333 def = defun.def_variable(node, node.TYPE == "SymbolVar" ? null : undefined);
334 }
335 if (!def.orig.every((sym) => {
336 if (sym === node) return true;
337 if (node instanceof AST_SymbolBlockDeclaration) {
338 return sym instanceof AST_SymbolLambda;
339 }
340 return !(sym instanceof AST_SymbolLet || sym instanceof AST_SymbolConst || sym instanceof AST_SymbolUsing);
341 })) {
342 js_error(
343 `"${node.name}" is redeclared`,
344 node.start.file,
345 node.start.line,
346 node.start.col,
347 node.start.pos
348 );
349 }
350 if (!(node instanceof AST_SymbolFunarg)) mark_export(def, 2);
351 if (defun !== scope) {
352 node.mark_enclosed();
353 var def = scope.find_variable(node);
354 if (node.thedef !== def) {
355 node.thedef = def;
356 node.reference();
357 }
358 }
359 } else if (node instanceof AST_LabelRef) {
360 var sym = labels.get(node.name);
361 if (!sym) throw new Error(string_template("Undefined label {name} [{line},{col}]", {
362 name: node.name,
363 line: node.start.line,
364 col: node.start.col
365 }));
366 node.thedef = sym;
367 }
368 if (!(scope instanceof AST_Toplevel) && (node instanceof AST_Export || node instanceof AST_Import)) {
369 js_error(
370 `"${node.TYPE}" statement may only appear at the top level`,
371 node.start.file,
372 node.start.line,
373 node.start.col,
374 node.start.pos
375 );
376 }
377 });
378
379 if (options.module) {
380 tw.directives["use strict"] = true;
381 }
382
383 this.walk(tw);
384
385 function mark_export(def, level) {
386 if (in_destructuring) {
387 var i = 0;
388 do {
389 level++;
390 } while (tw.parent(i++) !== in_destructuring);
391 }
392 var node = tw.parent(level);
393 if (def.export = node instanceof AST_Export ? MASK_EXPORT_DONT_MANGLE : 0) {
394 var exported = node.exported_definition;
395 if ((exported instanceof AST_Defun || exported instanceof AST_DefClass) && node.is_default) {
396 def.export = MASK_EXPORT_WANT_MANGLE;
397 }
398 }
399 }
400
401 // pass 2: find back references and eval
402 const is_toplevel = this instanceof AST_Toplevel;
403 if (is_toplevel) {
404 this.globals = new Map();
405 }
406
407 var tw = new TreeWalker(node => {
408 if (node instanceof AST_LoopControl && node.label) {
409 node.label.thedef.references.push(node);
410 return true;
411 }
412 if (node instanceof AST_SymbolRef) {
413 var name = node.name;
414 if (name == "eval" && tw.parent() instanceof AST_Call) {
415 for (var s = node.scope; s && !s.uses_eval; s = s.parent_scope) {
416 s.uses_eval = true;
417 }
418 }
419 var sym;
420 if (tw.parent() instanceof AST_NameMapping && tw.parent(1).module_name
421 || !(sym = node.scope.find_variable(name))) {
422
423 sym = toplevel.def_global(node);
424 if (node instanceof AST_SymbolExport) sym.export = MASK_EXPORT_DONT_MANGLE;
425 } else if (sym.scope instanceof AST_Lambda && name == "arguments") {
426 sym.scope.get_defun_scope().uses_arguments = true;
427 }
428 node.thedef = sym;
429 node.reference();
430 if (node.scope.is_block_scope()
431 && !(sym.orig[0] instanceof AST_SymbolBlockDeclaration)) {
432 node.scope = node.scope.get_defun_scope();
433 }
434 return true;
435 }
436 // ensure mangling works if catch reuses a scope variable
437 var def;
438 if (node instanceof AST_SymbolCatch && (def = redefined_catch_def(node.definition()))) {
439 var s = node.scope;
440 while (s) {
441 push_uniq(s.enclosed, def);
442 if (s === def.scope) break;
443 s = s.parent_scope;
444 }
445 }
446 });
447 this.walk(tw);
448
449 // pass 3: work around IE8 and Safari catch scope bugs
450 if (options.ie8 || options.safari10) {
451 walk(this, node => {
452 if (node instanceof AST_SymbolCatch) {
453 var name = node.name;
454 var refs = node.thedef.references;
455 var scope = node.scope.get_defun_scope();
456 var def = scope.find_variable(name)
457 || toplevel.globals.get(name)
458 || scope.def_variable(node);
459 refs.forEach(function(ref) {
460 ref.thedef = def;
461 ref.reference();
462 });
463 node.thedef = def;
464 node.reference();
465 return true;
466 }
467 });
468 }
469
470 // pass 4: add symbol definitions to loop scopes
471 // Safari/Webkit bug workaround - loop init let variable shadowing argument.
472 // https://github.com/mishoo/UglifyJS2/issues/1753
473 // https://bugs.webkit.org/show_bug.cgi?id=171041
474 if (options.safari10) {
475 for (const scope of for_scopes) {
476 scope.parent_scope.variables.forEach(function(def) {
477 push_uniq(scope.enclosed, def);
478 });
479 }
480 }
481});
482
483AST_Toplevel.DEFMETHOD("def_global", function(node) {
484 var globals = this.globals, name = node.name;
485 if (globals.has(name)) {
486 return globals.get(name);
487 } else {
488 var g = new SymbolDef(this, node);
489 g.undeclared = true;
490 g.global = true;
491 globals.set(name, g);
492 return g;
493 }
494});
495
496AST_Scope.DEFMETHOD("init_scope_vars", function(parent_scope) {
497 this.variables = new Map(); // map name to AST_SymbolVar (variables defined in this scope; includes functions)
498 this.uses_with = false; // will be set to true if this or some nested scope uses the `with` statement
499 this.uses_eval = false; // will be set to true if this or nested scope uses the global `eval`
500 this.parent_scope = parent_scope; // the parent scope
501 this.enclosed = []; // a list of variables from this or outer scope(s) that are referenced from this or inner scopes
502 this.cname = -1; // the current index for mangling functions/variables
503});
504
505AST_Scope.DEFMETHOD("conflicting_def", function (name) {
506 return (
507 this.enclosed.find(def => def.name === name)
508 || this.variables.has(name)
509 || (this.parent_scope && this.parent_scope.conflicting_def(name))
510 );
511});
512
513AST_Scope.DEFMETHOD("conflicting_def_shallow", function (name) {
514 return (
515 this.enclosed.find(def => def.name === name)
516 || this.variables.has(name)
517 );
518});
519
520AST_Scope.DEFMETHOD("add_child_scope", function (scope) {
521 // `scope` is going to be moved into `this` right now.
522 // Update the required scopes' information
523
524 if (scope.parent_scope === this) return;
525
526 scope.parent_scope = this;
527
528 // Propagate to this.uses_arguments from arrow functions
529 if ((scope instanceof AST_Arrow) && (this instanceof AST_Lambda && !this.uses_arguments)) {
530 this.uses_arguments = walk(scope, node => {
531 if (
532 node instanceof AST_SymbolRef
533 && node.scope instanceof AST_Lambda
534 && node.name === "arguments"
535 ) {
536 return walk_abort;
537 }
538
539 if (node instanceof AST_Lambda && !(node instanceof AST_Arrow)) {
540 return true;
541 }
542 });
543 }
544
545 this.uses_with = this.uses_with || scope.uses_with;
546 this.uses_eval = this.uses_eval || scope.uses_eval;
547
548 const scope_ancestry = (() => {
549 const ancestry = [];
550 let cur = this;
551 do {
552 ancestry.push(cur);
553 } while ((cur = cur.parent_scope));
554 ancestry.reverse();
555 return ancestry;
556 })();
557
558 const new_scope_enclosed_set = new Set(scope.enclosed);
559 const to_enclose = [];
560 for (const scope_topdown of scope_ancestry) {
561 to_enclose.forEach(e => push_uniq(scope_topdown.enclosed, e));
562 for (const def of scope_topdown.variables.values()) {
563 if (new_scope_enclosed_set.has(def)) {
564 push_uniq(to_enclose, def);
565 push_uniq(scope_topdown.enclosed, def);
566 }
567 }
568 }
569});
570
571function find_scopes_visible_from(scopes) {
572 const found_scopes = new Set();
573
574 for (const scope of new Set(scopes)) {
575 (function bubble_up(scope) {
576 if (scope == null || found_scopes.has(scope)) return;
577
578 found_scopes.add(scope);
579
580 bubble_up(scope.parent_scope);
581 })(scope);
582 }
583
584 return [...found_scopes];
585}
586
587// Creates a symbol during compression
588AST_Scope.DEFMETHOD("create_symbol", function(SymClass, {
589 source,
590 tentative_name,
591 scope,
592 conflict_scopes = [scope],
593 init = null
594} = {}) {
595 let symbol_name;
596
597 conflict_scopes = find_scopes_visible_from(conflict_scopes);
598
599 if (tentative_name) {
600 // Implement hygiene (no new names are conflicting with existing names)
601 tentative_name =
602 symbol_name =
603 tentative_name.replace(/(?:^[^a-z_$]|[^a-z0-9_$])/ig, "_");
604
605 let i = 0;
606 while (conflict_scopes.find(s => s.conflicting_def_shallow(symbol_name))) {
607 symbol_name = tentative_name + "$" + i++;
608 }
609 }
610
611 if (!symbol_name) {
612 throw new Error("No symbol name could be generated in create_symbol()");
613 }
614
615 const symbol = make_node(SymClass, source, {
616 name: symbol_name,
617 scope
618 });
619
620 this.def_variable(symbol, init || null);
621
622 symbol.mark_enclosed();
623
624 return symbol;
625});
626
627
628AST_Node.DEFMETHOD("is_block_scope", return_false);
629AST_Class.DEFMETHOD("is_block_scope", return_false);
630AST_Lambda.DEFMETHOD("is_block_scope", return_false);
631AST_Toplevel.DEFMETHOD("is_block_scope", return_false);
632AST_SwitchBranch.DEFMETHOD("is_block_scope", return_false);
633AST_Block.DEFMETHOD("is_block_scope", return_true);
634AST_Scope.DEFMETHOD("is_block_scope", function () {
635 return this._block_scope || false;
636});
637AST_IterationStatement.DEFMETHOD("is_block_scope", return_true);
638
639AST_Lambda.DEFMETHOD("init_scope_vars", function() {
640 AST_Scope.prototype.init_scope_vars.apply(this, arguments);
641 this.uses_arguments = false;
642 this.def_variable(new AST_SymbolFunarg({
643 name: "arguments",
644 start: this.start,
645 end: this.end
646 }));
647});
648
649AST_Arrow.DEFMETHOD("init_scope_vars", function() {
650 AST_Scope.prototype.init_scope_vars.apply(this, arguments);
651 this.uses_arguments = false;
652});
653
654AST_Symbol.DEFMETHOD("mark_enclosed", function() {
655 var def = this.definition();
656 var s = this.scope;
657 while (s) {
658 push_uniq(s.enclosed, def);
659 if (s === def.scope) break;
660 s = s.parent_scope;
661 }
662});
663
664AST_Symbol.DEFMETHOD("reference", function() {
665 this.definition().references.push(this);
666 this.mark_enclosed();
667});
668
669AST_Scope.DEFMETHOD("find_variable", function(name) {
670 if (name instanceof AST_Symbol) name = name.name;
671 return this.variables.get(name)
672 || (this.parent_scope && this.parent_scope.find_variable(name));
673});
674
675AST_Scope.DEFMETHOD("def_function", function(symbol, init) {
676 var def = this.def_variable(symbol, init);
677 if (!def.init || def.init instanceof AST_Defun) def.init = init;
678 return def;
679});
680
681AST_Scope.DEFMETHOD("def_variable", function(symbol, init) {
682 var def = this.variables.get(symbol.name);
683 if (def) {
684 def.orig.push(symbol);
685 if (def.init && (def.scope !== symbol.scope || def.init instanceof AST_Function)) {
686 def.init = init;
687 }
688 } else {
689 def = new SymbolDef(this, symbol, init);
690 this.variables.set(symbol.name, def);
691 def.global = !this.parent_scope;
692 }
693 return symbol.thedef = def;
694});
695
696function next_mangled(scope, options) {
697 let defun_scope;
698 if (
699 scopes_with_block_defuns
700 && (defun_scope = scope.get_defun_scope())
701 && scopes_with_block_defuns.has(defun_scope)
702 ) {
703 scope = defun_scope;
704 }
705
706 var ext = scope.enclosed;
707 var nth_identifier = options.nth_identifier;
708 out: while (true) {
709 var m = nth_identifier.get(++scope.cname);
710 if (ALL_RESERVED_WORDS.has(m)) continue; // skip over "do"
711
712 // https://github.com/mishoo/UglifyJS2/issues/242 -- do not
713 // shadow a name reserved from mangling.
714 if (options.reserved.has(m)) continue;
715
716 // Functions with short names might collide with base54 output
717 // and therefore cause collisions when keep_fnames is true.
718 if (unmangleable_names && unmangleable_names.has(m)) continue out;
719
720 // we must ensure that the mangled name does not shadow a name
721 // from some parent scope that is referenced in this or in
722 // inner scopes.
723 for (let i = ext.length; --i >= 0;) {
724 const def = ext[i];
725 const name = def.mangled_name || (def.unmangleable(options) && def.name);
726 if (m == name) continue out;
727 }
728 return m;
729 }
730}
731
732AST_Scope.DEFMETHOD("next_mangled", function(options) {
733 return next_mangled(this, options);
734});
735
736AST_Toplevel.DEFMETHOD("next_mangled", function(options) {
737 let name;
738 const mangled_names = this.mangled_names;
739 do {
740 name = next_mangled(this, options);
741 } while (mangled_names.has(name));
742 return name;
743});
744
745AST_Function.DEFMETHOD("next_mangled", function(options, def) {
746 // #179, #326
747 // in Safari strict mode, something like (function x(x){...}) is a syntax error;
748 // a function expression's argument cannot shadow the function expression's name
749
750 var tricky_def = def.orig[0] instanceof AST_SymbolFunarg && this.name && this.name.definition();
751
752 // the function's mangled_name is null when keep_fnames is true
753 var tricky_name = tricky_def ? tricky_def.mangled_name || tricky_def.name : null;
754
755 while (true) {
756 var name = next_mangled(this, options);
757 if (!tricky_name || tricky_name != name)
758 return name;
759 }
760});
761
762AST_Symbol.DEFMETHOD("unmangleable", function(options) {
763 var def = this.definition();
764 return !def || def.unmangleable(options);
765});
766
767// labels are always mangleable
768AST_Label.DEFMETHOD("unmangleable", return_false);
769
770AST_Symbol.DEFMETHOD("unreferenced", function() {
771 return !this.definition().references.length && !this.scope.pinned();
772});
773
774AST_Symbol.DEFMETHOD("definition", function() {
775 return this.thedef;
776});
777
778AST_Symbol.DEFMETHOD("global", function() {
779 return this.thedef.global;
780});
781
782/**
783 * Format the mangler options (if any) into their appropriate types
784 */
785export function format_mangler_options(options) {
786 options = defaults(options, {
787 eval : false,
788 nth_identifier : base54,
789 ie8 : false,
790 keep_classnames: false,
791 keep_fnames : false,
792 module : false,
793 reserved : [],
794 toplevel : false,
795 });
796 if (options.module) options.toplevel = true;
797 if (!Array.isArray(options.reserved)
798 && !(options.reserved instanceof Set)
799 ) {
800 options.reserved = [];
801 }
802 options.reserved = new Set(options.reserved);
803 // Never mangle arguments
804 options.reserved.add("arguments");
805 return options;
806}
807
808AST_Toplevel.DEFMETHOD("mangle_names", function(options) {
809 options = format_mangler_options(options);
810 var nth_identifier = options.nth_identifier;
811
812 // We only need to mangle declaration nodes. Special logic wired
813 // into the code generator will display the mangled name if it's
814 // present (and for AST_SymbolRef-s it'll use the mangled name of
815 // the AST_SymbolDeclaration that it points to).
816 var lname = -1;
817 var to_mangle = [];
818
819 if (options.keep_fnames) {
820 function_defs = new Set();
821 }
822
823 const mangled_names = this.mangled_names = new Set();
824 unmangleable_names = new Set();
825
826 if (options.cache) {
827 this.globals.forEach(collect);
828 if (options.cache.props) {
829 options.cache.props.forEach(function(mangled_name) {
830 mangled_names.add(mangled_name);
831 });
832 }
833 }
834
835 var tw = new TreeWalker(function(node, descend) {
836 if (node instanceof AST_LabeledStatement) {
837 // lname is incremented when we get to the AST_Label
838 var save_nesting = lname;
839 descend();
840 lname = save_nesting;
841 return true; // don't descend again in TreeWalker
842 }
843 if (
844 node instanceof AST_Defun
845 && !(tw.parent() instanceof AST_Scope)
846 ) {
847 scopes_with_block_defuns = scopes_with_block_defuns || new Set();
848 scopes_with_block_defuns.add(node.parent_scope.get_defun_scope());
849 }
850 if (node instanceof AST_Scope) {
851 node.variables.forEach(collect);
852 return;
853 }
854 if (node.is_block_scope()) {
855 node.block_scope.variables.forEach(collect);
856 return;
857 }
858 if (
859 function_defs
860 && node instanceof AST_VarDef
861 && node.name instanceof AST_Symbol
862 && node.value instanceof AST_Lambda
863 && !node.value.name
864 && keep_name(options.keep_fnames, node.name.name)
865 ) {
866 function_defs.add(node.name.definition().id);
867 return;
868 }
869 if (node instanceof AST_Label) {
870 let name;
871 do {
872 name = nth_identifier.get(++lname);
873 } while (ALL_RESERVED_WORDS.has(name));
874 node.mangled_name = name;
875 return true;
876 }
877 if (!(options.ie8 || options.safari10) && node instanceof AST_SymbolCatch) {
878 to_mangle.push(node.definition());
879 return;
880 }
881 });
882
883 this.walk(tw);
884
885 if (options.keep_fnames || options.keep_classnames) {
886 // Collect a set of short names which are unmangleable,
887 // for use in avoiding collisions in next_mangled.
888 to_mangle.forEach(def => {
889 if (def.name.length < 6 && def.unmangleable(options)) {
890 unmangleable_names.add(def.name);
891 }
892 });
893 }
894
895 to_mangle.forEach(def => { def.mangle(options); });
896
897 function_defs = null;
898 unmangleable_names = null;
899 scopes_with_block_defuns = null;
900
901 function collect(symbol) {
902 if (symbol.export & MASK_EXPORT_DONT_MANGLE) {
903 unmangleable_names.add(symbol.name);
904 } else if (!options.reserved.has(symbol.name)) {
905 to_mangle.push(symbol);
906 }
907 }
908});
909
910AST_Toplevel.DEFMETHOD("find_colliding_names", function(options) {
911 const cache = options.cache && options.cache.props;
912 const avoid = new Set();
913 options.reserved.forEach(to_avoid);
914 this.globals.forEach(add_def);
915 this.walk(new TreeWalker(function(node) {
916 if (node instanceof AST_Scope) node.variables.forEach(add_def);
917 if (node instanceof AST_SymbolCatch) add_def(node.definition());
918 }));
919 return avoid;
920
921 function to_avoid(name) {
922 avoid.add(name);
923 }
924
925 function add_def(def) {
926 var name = def.name;
927 if (def.global && cache && cache.has(name)) name = cache.get(name);
928 else if (!def.unmangleable(options)) return;
929 to_avoid(name);
930 }
931});
932
933AST_Toplevel.DEFMETHOD("expand_names", function(options) {
934 options = format_mangler_options(options);
935 var nth_identifier = options.nth_identifier;
936 if (nth_identifier.reset && nth_identifier.sort) {
937 nth_identifier.reset();
938 nth_identifier.sort();
939 }
940 var avoid = this.find_colliding_names(options);
941 var cname = 0;
942 this.globals.forEach(rename);
943 this.walk(new TreeWalker(function(node) {
944 if (node instanceof AST_Scope) node.variables.forEach(rename);
945 if (node instanceof AST_SymbolCatch) rename(node.definition());
946 }));
947
948 function next_name() {
949 var name;
950 do {
951 name = nth_identifier.get(cname++);
952 } while (avoid.has(name) || ALL_RESERVED_WORDS.has(name));
953 return name;
954 }
955
956 function rename(def) {
957 if (def.global && options.cache) return;
958 if (def.unmangleable(options)) return;
959 if (options.reserved.has(def.name)) return;
960 const redefinition = redefined_catch_def(def);
961 const name = def.name = redefinition ? redefinition.name : next_name();
962 def.orig.forEach(function(sym) {
963 sym.name = name;
964 });
965 def.references.forEach(function(sym) {
966 sym.name = name;
967 });
968 }
969});
970
971AST_Node.DEFMETHOD("tail_node", return_this);
972AST_Sequence.DEFMETHOD("tail_node", function() {
973 return this.expressions[this.expressions.length - 1];
974});
975
976AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options) {
977 options = format_mangler_options(options);
978 var nth_identifier = options.nth_identifier;
979 if (!nth_identifier.reset || !nth_identifier.consider || !nth_identifier.sort) {
980 // If the identifier mangler is invariant, skip computing character frequency.
981 return;
982 }
983 nth_identifier.reset();
984
985 try {
986 AST_Node.prototype.print = function(stream, force_parens) {
987 this._print(stream, force_parens);
988 if (this instanceof AST_Symbol && !this.unmangleable(options)) {
989 nth_identifier.consider(this.name, -1);
990 } else if (options.properties) {
991 if (this instanceof AST_DotHash) {
992 nth_identifier.consider("#" + this.property, -1);
993 } else if (this instanceof AST_Dot) {
994 nth_identifier.consider(this.property, -1);
995 } else if (this instanceof AST_Sub) {
996 skip_string(this.property);
997 }
998 }
999 };
1000 nth_identifier.consider(this.print_to_string(), 1);
1001 } finally {
1002 AST_Node.prototype.print = AST_Node.prototype._print;
1003 }
1004 nth_identifier.sort();
1005
1006 function skip_string(node) {
1007 if (node instanceof AST_String) {
1008 nth_identifier.consider(node.value, -1);
1009 } else if (node instanceof AST_Conditional) {
1010 skip_string(node.consequent);
1011 skip_string(node.alternative);
1012 } else if (node instanceof AST_Sequence) {
1013 skip_string(node.tail_node());
1014 }
1015 }
1016});
1017
1018const base54 = (() => {
1019 const leading = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_".split("");
1020 const digits = "0123456789".split("");
1021 let chars;
1022 let frequency;
1023 function reset() {
1024 frequency = new Map();
1025 leading.forEach(function(ch) {
1026 frequency.set(ch, 0);
1027 });
1028 digits.forEach(function(ch) {
1029 frequency.set(ch, 0);
1030 });
1031 }
1032 function consider(str, delta) {
1033 for (var i = str.length; --i >= 0;) {
1034 frequency.set(str[i], frequency.get(str[i]) + delta);
1035 }
1036 }
1037 function compare(a, b) {
1038 return frequency.get(b) - frequency.get(a);
1039 }
1040 function sort() {
1041 chars = mergeSort(leading, compare).concat(mergeSort(digits, compare));
1042 }
1043 // Ensure this is in a usable initial state.
1044 reset();
1045 sort();
1046 function base54(num) {
1047 var ret = "", base = 54;
1048 num++;
1049 do {
1050 num--;
1051 ret += chars[num % base];
1052 num = Math.floor(num / base);
1053 base = 64;
1054 } while (num > 0);
1055 return ret;
1056 }
1057
1058 return {
1059 get: base54,
1060 consider,
1061 reset,
1062 sort
1063 };
1064})();
1065
1066export {
1067 base54,
1068 SymbolDef,
1069};
Note: See TracBrowser for help on using the repository browser.