UNPKG

11 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/* global global, self */
46
47import {
48 defaults,
49 push_uniq,
50} from "./utils/index.js";
51import { base54 } from "./scope.js";
52import {
53 AST_Binary,
54 AST_Call,
55 AST_Conditional,
56 AST_Dot,
57 AST_ObjectKeyVal,
58 AST_ObjectProperty,
59 AST_Sequence,
60 AST_String,
61 AST_Sub,
62 TreeTransformer,
63 TreeWalker,
64} from "./ast.js";
65import { domprops } from "../tools/domprops.js";
66
67function find_builtins(reserved) {
68 domprops.forEach(add);
69
70 // Compatibility fix for some standard defined globals not defined on every js environment
71 var new_globals = ["Symbol", "Map", "Promise", "Proxy", "Reflect", "Set", "WeakMap", "WeakSet"];
72 var objects = {};
73 var global_ref = typeof global === "object" ? global : self;
74
75 new_globals.forEach(function (new_global) {
76 objects[new_global] = global_ref[new_global] || new Function();
77 });
78
79 [
80 "null",
81 "true",
82 "false",
83 "NaN",
84 "Infinity",
85 "-Infinity",
86 "undefined",
87 ].forEach(add);
88 [ Object, Array, Function, Number,
89 String, Boolean, Error, Math,
90 Date, RegExp, objects.Symbol, ArrayBuffer,
91 DataView, decodeURI, decodeURIComponent,
92 encodeURI, encodeURIComponent, eval, EvalError,
93 Float32Array, Float64Array, Int8Array, Int16Array,
94 Int32Array, isFinite, isNaN, JSON, objects.Map, parseFloat,
95 parseInt, objects.Promise, objects.Proxy, RangeError, ReferenceError,
96 objects.Reflect, objects.Set, SyntaxError, TypeError, Uint8Array,
97 Uint8ClampedArray, Uint16Array, Uint32Array, URIError,
98 objects.WeakMap, objects.WeakSet
99 ].forEach(function(ctor) {
100 Object.getOwnPropertyNames(ctor).map(add);
101 if (ctor.prototype) {
102 Object.getOwnPropertyNames(ctor.prototype).map(add);
103 }
104 });
105 function add(name) {
106 reserved.add(name);
107 }
108}
109
110function reserve_quoted_keys(ast, reserved) {
111 function add(name) {
112 push_uniq(reserved, name);
113 }
114
115 ast.walk(new TreeWalker(function(node) {
116 if (node instanceof AST_ObjectKeyVal && node.quote) {
117 add(node.key);
118 } else if (node instanceof AST_ObjectProperty && node.quote) {
119 add(node.key.name);
120 } else if (node instanceof AST_Sub) {
121 addStrings(node.property, add);
122 }
123 }));
124}
125
126function addStrings(node, add) {
127 node.walk(new TreeWalker(function(node) {
128 if (node instanceof AST_Sequence) {
129 addStrings(node.tail_node(), add);
130 } else if (node instanceof AST_String) {
131 add(node.value);
132 } else if (node instanceof AST_Conditional) {
133 addStrings(node.consequent, add);
134 addStrings(node.alternative, add);
135 }
136 return true;
137 }));
138}
139
140function mangle_properties(ast, options) {
141 options = defaults(options, {
142 builtins: false,
143 cache: null,
144 debug: false,
145 keep_quoted: false,
146 only_cache: false,
147 regex: null,
148 reserved: null,
149 undeclared: false,
150 }, true);
151
152 var reserved_option = options.reserved;
153 if (!Array.isArray(reserved_option)) reserved_option = [reserved_option];
154 var reserved = new Set(reserved_option);
155 if (!options.builtins) find_builtins(reserved);
156
157 var cname = -1;
158 var cache;
159 if (options.cache) {
160 cache = options.cache.props;
161 cache.forEach(function(mangled_name) {
162 reserved.add(mangled_name);
163 });
164 } else {
165 cache = new Map();
166 }
167
168 var regex = options.regex && new RegExp(options.regex);
169
170 // note debug is either false (disabled), or a string of the debug suffix to use (enabled).
171 // note debug may be enabled as an empty string, which is falsey. Also treat passing 'true'
172 // the same as passing an empty string.
173 var debug = options.debug !== false;
174 var debug_name_suffix;
175 if (debug) {
176 debug_name_suffix = (options.debug === true ? "" : options.debug);
177 }
178
179 var names_to_mangle = new Set();
180 var unmangleable = new Set();
181
182 var keep_quoted_strict = options.keep_quoted === "strict";
183
184 // step 1: find candidates to mangle
185 ast.walk(new TreeWalker(function(node) {
186 if (node instanceof AST_ObjectKeyVal) {
187 if (typeof node.key == "string" &&
188 (!keep_quoted_strict || !node.quote)) {
189 add(node.key);
190 }
191 } else if (node instanceof AST_ObjectProperty) {
192 // setter or getter, since KeyVal is handled above
193 if (!keep_quoted_strict || !node.key.end.quote) {
194 add(node.key.name);
195 }
196 } else if (node instanceof AST_Dot) {
197 var declared = !!options.undeclared;
198 if (!declared) {
199 var root = node;
200 while (root.expression) {
201 root = root.expression;
202 }
203 declared = !(root.thedef && root.thedef.undeclared);
204 }
205 if (declared &&
206 (!keep_quoted_strict || !node.quote)) {
207 add(node.property);
208 }
209 } else if (node instanceof AST_Sub) {
210 if (!keep_quoted_strict) {
211 addStrings(node.property, add);
212 }
213 } else if (node instanceof AST_Call
214 && node.expression.print_to_string() == "Object.defineProperty") {
215 addStrings(node.args[1], add);
216 } else if (node instanceof AST_Binary && node.operator === "in") {
217 addStrings(node.left, add);
218 }
219 }));
220
221 // step 2: transform the tree, renaming properties
222 return ast.transform(new TreeTransformer(function(node) {
223 if (node instanceof AST_ObjectKeyVal) {
224 if (typeof node.key == "string" &&
225 (!keep_quoted_strict || !node.quote)) {
226 node.key = mangle(node.key);
227 }
228 } else if (node instanceof AST_ObjectProperty) {
229 // setter, getter, method or class field
230 if (!keep_quoted_strict || !node.key.end.quote) {
231 node.key.name = mangle(node.key.name);
232 }
233 } else if (node instanceof AST_Dot) {
234 if (!keep_quoted_strict || !node.quote) {
235 node.property = mangle(node.property);
236 }
237 } else if (!options.keep_quoted && node instanceof AST_Sub) {
238 node.property = mangleStrings(node.property);
239 } else if (node instanceof AST_Call
240 && node.expression.print_to_string() == "Object.defineProperty") {
241 node.args[1] = mangleStrings(node.args[1]);
242 } else if (node instanceof AST_Binary && node.operator === "in") {
243 node.left = mangleStrings(node.left);
244 }
245 }));
246
247 // only function declarations after this line
248
249 function can_mangle(name) {
250 if (unmangleable.has(name)) return false;
251 if (reserved.has(name)) return false;
252 if (options.only_cache) {
253 return cache.has(name);
254 }
255 if (/^-?[0-9]+(\.[0-9]+)?(e[+-][0-9]+)?$/.test(name)) return false;
256 return true;
257 }
258
259 function should_mangle(name) {
260 if (regex && !regex.test(name)) return false;
261 if (reserved.has(name)) return false;
262 return cache.has(name)
263 || names_to_mangle.has(name);
264 }
265
266 function add(name) {
267 if (can_mangle(name))
268 names_to_mangle.add(name);
269
270 if (!should_mangle(name)) {
271 unmangleable.add(name);
272 }
273 }
274
275 function mangle(name) {
276 if (!should_mangle(name)) {
277 return name;
278 }
279
280 var mangled = cache.get(name);
281 if (!mangled) {
282 if (debug) {
283 // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_.
284 var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_";
285
286 if (can_mangle(debug_mangled)) {
287 mangled = debug_mangled;
288 }
289 }
290
291 // either debug mode is off, or it is on and we could not use the mangled name
292 if (!mangled) {
293 do {
294 mangled = base54(++cname);
295 } while (!can_mangle(mangled));
296 }
297
298 cache.set(name, mangled);
299 }
300 return mangled;
301 }
302
303 function mangleStrings(node) {
304 return node.transform(new TreeTransformer(function(node) {
305 if (node instanceof AST_Sequence) {
306 var last = node.expressions.length - 1;
307 node.expressions[last] = mangleStrings(node.expressions[last]);
308 } else if (node instanceof AST_String) {
309 node.value = mangle(node.value);
310 } else if (node instanceof AST_Conditional) {
311 node.consequent = mangleStrings(node.consequent);
312 node.alternative = mangleStrings(node.alternative);
313 }
314 return node;
315 }));
316 }
317}
318
319export {
320 reserve_quoted_keys,
321 mangle_properties,
322};