1/***********************************************************************
2
3  A JavaScript tokenizer / parser / beautifier / compressor.
4  https://github.com/mishoo/UglifyJS
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
46function get_builtins() {
47    var names = new Dictionary();
48    // constants
49    [
50        "NaN",
51        "null",
52        "true",
53        "false",
54        "Infinity",
55        "-Infinity",
56        "undefined",
57    ].forEach(add);
58    // global functions
59    [
60        "encodeURI",
61        "encodeURIComponent",
62        "escape",
63        "eval",
64        "decodeURI",
65        "decodeURIComponent",
66        "isFinite",
67        "isNaN",
68        "parseFloat",
69        "parseInt",
70        "unescape",
71    ].forEach(add);
72    // global constructors & objects
73    var global = Function("return this")();
74    [
75        "Array",
76        "ArrayBuffer",
77        "Atomics",
78        "BigInt",
79        "Boolean",
80        "console",
81        "DataView",
82        "Date",
83        "Error",
84        "Function",
85        "Int8Array",
86        "Intl",
87        "JSON",
88        "Map",
89        "Math",
90        "Number",
91        "Object",
92        "Promise",
93        "Proxy",
94        "Reflect",
95        "RegExp",
96        "Set",
97        "String",
98        "Symbol",
99        "WebAssembly",
100    ].forEach(function(name) {
101        add(name);
102        var ctor = global[name];
103        if (!ctor) return;
104        Object.getOwnPropertyNames(ctor).map(add);
105        if (typeof ctor != "function") return;
106        if (ctor.__proto__) Object.getOwnPropertyNames(ctor.__proto__).map(add);
107        if (ctor.prototype) Object.getOwnPropertyNames(ctor.prototype).map(add);
108        try {
109            Object.getOwnPropertyNames(new ctor()).map(add);
110        } catch (e) {
111            try {
112                Object.getOwnPropertyNames(ctor()).map(add);
113            } catch (e) {}
114        }
115    });
116    return (get_builtins = function() {
117        return names.clone();
118    })();
119
120    function add(name) {
121        names.set(name, true);
122    }
123}
124
125function reserve_quoted_keys(ast, reserved) {
126    ast.walk(new TreeWalker(function(node) {
127        if (node instanceof AST_ClassProperty
128            || node instanceof AST_DestructuredKeyVal
129            || node instanceof AST_ObjectProperty) {
130            if (node.key instanceof AST_Node) {
131                addStrings(node.key, add);
132            } else if (node.start && node.start.quote) {
133                add(node.key);
134            }
135        } else if (node instanceof AST_Dot) {
136            if (node.quoted) add(node.property);
137        } else if (node instanceof AST_Sub) {
138            addStrings(node.property, add);
139        }
140    }));
141
142    function add(name) {
143        push_uniq(reserved, name);
144    }
145}
146
147function addStrings(node, add) {
148    if (node instanceof AST_Conditional) {
149        addStrings(node.consequent, add);
150        addStrings(node.alternative, add);
151    } else if (node instanceof AST_Sequence) {
152        addStrings(node.tail_node(), add);
153    } else if (node instanceof AST_String) {
154        add(node.value);
155    }
156}
157
158function mangle_properties(ast, options) {
159    options = defaults(options, {
160        builtins: false,
161        cache: null,
162        debug: false,
163        domprops: false,
164        keep_quoted: false,
165        regex: null,
166        reserved: null,
167    }, true);
168
169    var reserved = options.builtins ? new Dictionary() : get_builtins();
170    if (!options.domprops && typeof domprops !== "undefined") domprops.forEach(function(name) {
171        reserved.set(name, true);
172    });
173    if (Array.isArray(options.reserved)) options.reserved.forEach(function(name) {
174        reserved.set(name, true);
175    });
176
177    var cname = -1;
178    var cache;
179    if (options.cache) {
180        cache = options.cache.props;
181        cache.each(function(name) {
182            reserved.set(name, true);
183        });
184    } else {
185        cache = new Dictionary();
186    }
187
188    var regex = options.regex;
189
190    // note debug is either false (disabled), or a string of the debug suffix to use (enabled).
191    // note debug may be enabled as an empty string, which is falsy. Also treat passing 'true'
192    // the same as passing an empty string.
193    var debug = options.debug !== false;
194    var debug_suffix;
195    if (debug) debug_suffix = options.debug === true ? "" : options.debug;
196
197    var names_to_mangle = new Dictionary();
198    var unmangleable = reserved.clone();
199
200    // step 1: find candidates to mangle
201    ast.walk(new TreeWalker(function(node) {
202        if (node.TYPE == "Call") {
203            var exp = node.expression;
204            if (exp instanceof AST_Dot) switch (exp.property) {
205              case "defineProperty":
206              case "getOwnPropertyDescriptor":
207                if (node.args.length < 2) break;
208                exp = exp.expression;
209                if (!(exp instanceof AST_SymbolRef)) break;
210                if (exp.name != "Object") break;
211                if (!exp.definition().undeclared) break;
212                addStrings(node.args[1], add);
213                break;
214              case "hasOwnProperty":
215                if (node.args.length < 1) break;
216                addStrings(node.args[0], add);
217                break;
218            }
219        } else if (node instanceof AST_ClassProperty
220            || node instanceof AST_DestructuredKeyVal
221            || node instanceof AST_ObjectProperty) {
222            if (node.key instanceof AST_Node) {
223                addStrings(node.key, add);
224            } else {
225                add(node.key);
226            }
227        } else if (node instanceof AST_Dot) {
228            if (is_lhs(node, this.parent())) add(node.property);
229        } else if (node instanceof AST_Sub) {
230            if (is_lhs(node, this.parent())) addStrings(node.property, add);
231        }
232    }));
233
234    // step 2: renaming properties
235    ast.walk(new TreeWalker(function(node) {
236        if (node instanceof AST_Binary) {
237            if (node.operator == "in") mangleStrings(node.left);
238        } else if (node.TYPE == "Call") {
239            var exp = node.expression;
240            if (exp instanceof AST_Dot) switch (exp.property) {
241              case "defineProperty":
242              case "getOwnPropertyDescriptor":
243                if (node.args.length < 2) break;
244                exp = exp.expression;
245                if (!(exp instanceof AST_SymbolRef)) break;
246                if (exp.name != "Object") break;
247                if (!exp.definition().undeclared) break;
248                mangleStrings(node.args[1]);
249                break;
250              case "hasOwnProperty":
251                if (node.args.length < 1) break;
252                mangleStrings(node.args[0]);
253                break;
254            }
255        } else if (node instanceof AST_ClassProperty
256            || node instanceof AST_DestructuredKeyVal
257            || node instanceof AST_ObjectProperty) {
258            if (node.key instanceof AST_Node) {
259                mangleStrings(node.key);
260            } else {
261                node.key = mangle(node.key);
262            }
263        } else if (node instanceof AST_Dot) {
264            node.property = mangle(node.property);
265        } else if (node instanceof AST_Sub) {
266            if (!options.keep_quoted) mangleStrings(node.property);
267        }
268    }));
269
270    // only function declarations after this line
271
272    function can_mangle(name) {
273        if (unmangleable.has(name)) return false;
274        if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
275        return true;
276    }
277
278    function should_mangle(name) {
279        if (reserved.has(name)) {
280            AST_Node.info("Preserving reserved property {this}", name);
281            return false;
282        }
283        if (regex && !regex.test(name)) {
284            AST_Node.info("Preserving excluded property {this}", name);
285            return false;
286        }
287        return cache.has(name) || names_to_mangle.has(name);
288    }
289
290    function add(name) {
291        if (can_mangle(name)) names_to_mangle.set(name, true);
292        if (!should_mangle(name)) unmangleable.set(name, true);
293    }
294
295    function mangle(name) {
296        if (!should_mangle(name)) return name;
297        var mangled = cache.get(name);
298        if (!mangled) {
299            if (debug) {
300                // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo ---> o._$foo$NNN_.
301                var debug_mangled = "_$" + name + "$" + debug_suffix + "_";
302                if (can_mangle(debug_mangled)) mangled = debug_mangled;
303            }
304            // either debug mode is off, or it is on and we could not use the mangled name
305            if (!mangled) do {
306                mangled = base54(++cname);
307            } while (!can_mangle(mangled));
308            if (/^#/.test(name)) mangled = "#" + mangled;
309            cache.set(name, mangled);
310        }
311        AST_Node.info("Mapping property {name} to {mangled}", {
312            mangled: mangled,
313            name: name,
314        });
315        return mangled;
316    }
317
318    function mangleStrings(node) {
319        if (node instanceof AST_Sequence) {
320            mangleStrings(node.tail_node());
321        } else if (node instanceof AST_String) {
322            node.value = mangle(node.value);
323        } else if (node instanceof AST_Conditional) {
324            mangleStrings(node.consequent);
325            mangleStrings(node.alternative);
326        }
327    }
328}
329