UNPKG

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