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