source: imaps-frontend/node_modules/terser/lib/compress/drop-unused.js

main
Last change on this file was 79a0317, checked in by stefan toskovski <stefantoska84@…>, 3 days ago

F4 Finalna Verzija

  • Property mode set to 100644
File size: 20.6 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
44import {
45 AST_Accessor,
46 AST_Assign,
47 AST_BlockStatement,
48 AST_Call,
49 AST_Class,
50 AST_ClassExpression,
51 AST_ClassStaticBlock,
52 AST_DefaultAssign,
53 AST_DefClass,
54 AST_Definitions,
55 AST_Defun,
56 AST_Destructuring,
57 AST_EmptyStatement,
58 AST_Expansion,
59 AST_Export,
60 AST_For,
61 AST_ForIn,
62 AST_Function,
63 AST_LabeledStatement,
64 AST_Lambda,
65 AST_Number,
66 AST_Scope,
67 AST_Sequence,
68 AST_SimpleStatement,
69 AST_SymbolBlockDeclaration,
70 AST_SymbolCatch,
71 AST_SymbolDeclaration,
72 AST_SymbolFunarg,
73 AST_SymbolRef,
74 AST_SymbolVar,
75 AST_Toplevel,
76 AST_Unary,
77 AST_Var,
78
79 TreeTransformer,
80 TreeWalker,
81 walk,
82} from "../ast.js";
83import {
84 keep_name,
85 make_node,
86 map_add,
87 MAP,
88 remove,
89 return_false,
90} from "../utils/index.js";
91import { SymbolDef } from "../scope.js";
92
93import {
94 WRITE_ONLY,
95 UNUSED,
96
97 has_flag,
98 set_flag,
99} from "./compressor-flags.js";
100import {
101 make_sequence,
102 maintain_this_binding,
103 is_empty,
104 is_ref_of,
105 can_be_evicted_from_block,
106} from "./common.js";
107import { is_used_in_expression } from "./inference.js";
108
109const r_keep_assign = /keep_assign/;
110
111/** Drop unused variables from this scope */
112AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
113 if (!compressor.option("unused")) return;
114 if (compressor.has_directive("use asm")) return;
115 if (!this.variables) return; // not really a scope (eg: AST_Class)
116
117 var self = this;
118 if (self.pinned()) return;
119 var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
120 var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
121 const assign_as_unused = r_keep_assign.test(compressor.option("unused")) ? return_false : function(node) {
122 if (node instanceof AST_Assign
123 && !node.logical
124 && (has_flag(node, WRITE_ONLY) || node.operator == "=")
125 ) {
126 return node.left;
127 }
128 if (node instanceof AST_Unary && has_flag(node, WRITE_ONLY)) {
129 return node.expression;
130 }
131 };
132 var in_use_ids = new Map();
133 var fixed_ids = new Map();
134 if (self instanceof AST_Toplevel && compressor.top_retain) {
135 self.variables.forEach(function(def) {
136 if (compressor.top_retain(def)) {
137 in_use_ids.set(def.id, def);
138 }
139 });
140 }
141 var var_defs_by_id = new Map();
142 var initializations = new Map();
143
144 // pass 1: find out which symbols are directly used in
145 // this scope (not in nested scopes).
146 var scope = this;
147 var tw = new TreeWalker(function(node, descend) {
148 if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) {
149 node.argnames.forEach(function(argname) {
150 if (!(argname instanceof AST_SymbolDeclaration)) return;
151 var def = argname.definition();
152 in_use_ids.set(def.id, def);
153 });
154 }
155 if (node === self) return;
156 if (node instanceof AST_Class && node.has_side_effects(compressor)) {
157 if (node.is_self_referential()) {
158 descend();
159 } else {
160 node.visit_nondeferred_class_parts(tw);
161 }
162 }
163 if (node instanceof AST_Defun || node instanceof AST_DefClass) {
164 var node_def = node.name.definition();
165 const in_export = tw.parent() instanceof AST_Export;
166 if (in_export || !drop_funcs && scope === self) {
167 if (node_def.global) {
168 in_use_ids.set(node_def.id, node_def);
169 }
170 }
171
172 map_add(initializations, node_def.id, node);
173 return true; // don't go in nested scopes
174 }
175 // In the root scope, we drop things. In inner scopes, we just check for uses.
176 const in_root_scope = scope === self;
177 if (node instanceof AST_SymbolFunarg && in_root_scope) {
178 map_add(var_defs_by_id, node.definition().id, node);
179 }
180 if (node instanceof AST_Definitions && in_root_scope) {
181 const in_export = tw.parent() instanceof AST_Export;
182 node.definitions.forEach(function(def) {
183 if (def.name instanceof AST_SymbolVar) {
184 map_add(var_defs_by_id, def.name.definition().id, def);
185 }
186 if (in_export || !drop_vars) {
187 walk(def.name, node => {
188 if (node instanceof AST_SymbolDeclaration) {
189 const def = node.definition();
190 if (def.global) {
191 in_use_ids.set(def.id, def);
192 }
193 }
194 });
195 }
196 if (def.name instanceof AST_Destructuring) {
197 def.walk(tw);
198 }
199 if (def.name instanceof AST_SymbolDeclaration && def.value) {
200 var node_def = def.name.definition();
201 map_add(initializations, node_def.id, def.value);
202 if (!node_def.chained && def.name.fixed_value() === def.value) {
203 fixed_ids.set(node_def.id, def);
204 }
205 if (def.value.has_side_effects(compressor)) {
206 def.value.walk(tw);
207 }
208 }
209 });
210 return true;
211 }
212 return scan_ref_scoped(node, descend);
213 });
214 self.walk(tw);
215 // pass 2: for every used symbol we need to walk its
216 // initialization code to figure out if it uses other
217 // symbols (that may not be in_use).
218 tw = new TreeWalker(scan_ref_scoped);
219 in_use_ids.forEach(function (def) {
220 var init = initializations.get(def.id);
221 if (init) init.forEach(function(init) {
222 init.walk(tw);
223 });
224 });
225 // pass 3: we should drop declarations not in_use
226 var tt = new TreeTransformer(
227 function before(node, descend, in_list) {
228 var parent = tt.parent();
229 if (drop_vars) {
230 const sym = assign_as_unused(node);
231 if (sym instanceof AST_SymbolRef) {
232 var def = sym.definition();
233 var in_use = in_use_ids.has(def.id);
234 if (node instanceof AST_Assign) {
235 if (!in_use || fixed_ids.has(def.id) && fixed_ids.get(def.id) !== node) {
236 const assignee = node.right.transform(tt);
237 if (!in_use && !assignee.has_side_effects(compressor) && !is_used_in_expression(tt)) {
238 return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
239 }
240 return maintain_this_binding(parent, node, assignee);
241 }
242 } else if (!in_use) {
243 return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
244 }
245 }
246 }
247 if (scope !== self) return;
248 var def;
249 if (node.name
250 && (node instanceof AST_ClassExpression
251 && !keep_name(compressor.option("keep_classnames"), (def = node.name.definition()).name)
252 || node instanceof AST_Function
253 && !keep_name(compressor.option("keep_fnames"), (def = node.name.definition()).name))) {
254 // any declarations with same name will overshadow
255 // name of this anonymous function and can therefore
256 // never be used anywhere
257 if (!in_use_ids.has(def.id) || def.orig.length > 1) node.name = null;
258 }
259 if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
260 var trim =
261 !compressor.option("keep_fargs")
262 // Is this an IIFE that won't refer to its name?
263 || parent instanceof AST_Call
264 && parent.expression === node
265 && !node.pinned()
266 && (!node.name || node.name.unreferenced());
267 for (var a = node.argnames, i = a.length; --i >= 0;) {
268 var sym = a[i];
269 if (sym instanceof AST_Expansion) {
270 sym = sym.expression;
271 }
272 if (sym instanceof AST_DefaultAssign) {
273 sym = sym.left;
274 }
275 // Do not drop destructuring arguments.
276 // They constitute a type assertion of sorts
277 if (
278 !(sym instanceof AST_Destructuring)
279 && !in_use_ids.has(sym.definition().id)
280 ) {
281 set_flag(sym, UNUSED);
282 if (trim) {
283 a.pop();
284 }
285 } else {
286 trim = false;
287 }
288 }
289 }
290 if (node instanceof AST_DefClass && node !== self) {
291 const def = node.name.definition();
292 descend(node, this);
293 const keep_class = def.global && !drop_funcs || in_use_ids.has(def.id);
294 if (!keep_class) {
295 const kept = node.drop_side_effect_free(compressor);
296 if (kept == null) {
297 def.eliminated++;
298 return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
299 }
300 return kept;
301 }
302 return node;
303 }
304 if (node instanceof AST_Defun && node !== self) {
305 const def = node.name.definition();
306 const keep = def.global && !drop_funcs || in_use_ids.has(def.id);
307 if (!keep) {
308 def.eliminated++;
309 return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
310 }
311 }
312 if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
313 var drop_block = !(parent instanceof AST_Toplevel) && !(node instanceof AST_Var);
314 // place uninitialized names at the start
315 var body = [], head = [], tail = [];
316 // for unused names whose initialization has
317 // side effects, we can cascade the init. code
318 // into the next one, or next statement.
319 var side_effects = [];
320 node.definitions.forEach(function(def) {
321 if (def.value) def.value = def.value.transform(tt);
322 var is_destructure = def.name instanceof AST_Destructuring;
323 var sym = is_destructure
324 ? new SymbolDef(null, { name: "<destructure>" }) /* fake SymbolDef */
325 : def.name.definition();
326 if (drop_block && sym.global) return tail.push(def);
327 if (!(drop_vars || drop_block)
328 || is_destructure
329 && (def.name.names.length
330 || def.name.is_array
331 || compressor.option("pure_getters") != true)
332 || in_use_ids.has(sym.id)
333 ) {
334 if (def.value && fixed_ids.has(sym.id) && fixed_ids.get(sym.id) !== def) {
335 def.value = def.value.drop_side_effect_free(compressor);
336 }
337 if (def.name instanceof AST_SymbolVar) {
338 var var_defs = var_defs_by_id.get(sym.id);
339 if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
340 if (def.value) {
341 var ref = make_node(AST_SymbolRef, def.name, def.name);
342 sym.references.push(ref);
343 var assign = make_node(AST_Assign, def, {
344 operator: "=",
345 logical: false,
346 left: ref,
347 right: def.value
348 });
349 if (fixed_ids.get(sym.id) === def) {
350 fixed_ids.set(sym.id, assign);
351 }
352 side_effects.push(assign.transform(tt));
353 }
354 remove(var_defs, def);
355 sym.eliminated++;
356 return;
357 }
358 }
359 if (def.value) {
360 if (side_effects.length > 0) {
361 if (tail.length > 0) {
362 side_effects.push(def.value);
363 def.value = make_sequence(def.value, side_effects);
364 } else {
365 body.push(make_node(AST_SimpleStatement, node, {
366 body: make_sequence(node, side_effects)
367 }));
368 }
369 side_effects = [];
370 }
371 tail.push(def);
372 } else {
373 head.push(def);
374 }
375 } else if (sym.orig[0] instanceof AST_SymbolCatch) {
376 var value = def.value && def.value.drop_side_effect_free(compressor);
377 if (value) side_effects.push(value);
378 def.value = null;
379 head.push(def);
380 } else {
381 var value = def.value && def.value.drop_side_effect_free(compressor);
382 if (value) {
383 side_effects.push(value);
384 }
385 sym.eliminated++;
386 }
387 });
388 if (head.length > 0 || tail.length > 0) {
389 node.definitions = head.concat(tail);
390 body.push(node);
391 }
392 if (side_effects.length > 0) {
393 body.push(make_node(AST_SimpleStatement, node, {
394 body: make_sequence(node, side_effects)
395 }));
396 }
397 switch (body.length) {
398 case 0:
399 return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
400 case 1:
401 return body[0];
402 default:
403 return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { body });
404 }
405 }
406 // certain combination of unused name + side effect leads to:
407 // https://github.com/mishoo/UglifyJS2/issues/44
408 // https://github.com/mishoo/UglifyJS2/issues/1830
409 // https://github.com/mishoo/UglifyJS2/issues/1838
410 // that's an invalid AST.
411 // We fix it at this stage by moving the `var` outside the `for`.
412 if (node instanceof AST_For) {
413 descend(node, this);
414 var block;
415 if (node.init instanceof AST_BlockStatement) {
416 block = node.init;
417 node.init = block.body.pop();
418 block.body.push(node);
419 }
420 if (node.init instanceof AST_SimpleStatement) {
421 node.init = node.init.body;
422 } else if (is_empty(node.init)) {
423 node.init = null;
424 }
425 return !block ? node : in_list ? MAP.splice(block.body) : block;
426 }
427 if (node instanceof AST_LabeledStatement
428 && node.body instanceof AST_For
429 ) {
430 descend(node, this);
431 if (node.body instanceof AST_BlockStatement) {
432 var block = node.body;
433 node.body = block.body.pop();
434 block.body.push(node);
435 return in_list ? MAP.splice(block.body) : block;
436 }
437 return node;
438 }
439 if (node instanceof AST_BlockStatement) {
440 descend(node, this);
441 if (in_list && node.body.every(can_be_evicted_from_block)) {
442 return MAP.splice(node.body);
443 }
444 return node;
445 }
446 if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
447 const save_scope = scope;
448 scope = node;
449 descend(node, this);
450 scope = save_scope;
451 return node;
452 }
453 },
454 function after(node, in_list) {
455 if (node instanceof AST_Sequence) {
456 switch (node.expressions.length) {
457 case 0: return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
458 case 1: return node.expressions[0];
459 }
460 }
461 }
462 );
463
464 self.transform(tt);
465
466 function scan_ref_scoped(node, descend) {
467 var node_def;
468 const sym = assign_as_unused(node);
469 if (sym instanceof AST_SymbolRef
470 && !is_ref_of(node.left, AST_SymbolBlockDeclaration)
471 && self.variables.get(sym.name) === (node_def = sym.definition())
472 ) {
473 if (node instanceof AST_Assign) {
474 node.right.walk(tw);
475 if (!node_def.chained && node.left.fixed_value() === node.right) {
476 fixed_ids.set(node_def.id, node);
477 }
478 }
479 return true;
480 }
481 if (node instanceof AST_SymbolRef) {
482 node_def = node.definition();
483 if (!in_use_ids.has(node_def.id)) {
484 in_use_ids.set(node_def.id, node_def);
485 if (node_def.orig[0] instanceof AST_SymbolCatch) {
486 const redef = node_def.scope.is_block_scope()
487 && node_def.scope.get_defun_scope().variables.get(node_def.name);
488 if (redef) in_use_ids.set(redef.id, redef);
489 }
490 }
491 return true;
492 }
493 if (node instanceof AST_Class) {
494 descend();
495 return true;
496 }
497 if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
498 var save_scope = scope;
499 scope = node;
500 descend();
501 scope = save_scope;
502 return true;
503 }
504 }
505});
Note: See TracBrowser for help on using the repository browser.