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