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

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • 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
216 // pass 2: for every used symbol we need to walk its
217 // initialization code to figure out if it uses other
218 // symbols (that may not be in_use).
219 tw = new TreeWalker(scan_ref_scoped);
220 in_use_ids.forEach(function (def) {
221 var init = initializations.get(def.id);
222 if (init) init.forEach(function(init) {
223 init.walk(tw);
224 });
225 });
226
227 // pass 3: we should drop declarations not in_use
228 var tt = new TreeTransformer(
229 function before(node, descend, in_list) {
230 var parent = tt.parent();
231 if (drop_vars) {
232 const sym = assign_as_unused(node);
233 if (sym instanceof AST_SymbolRef) {
234 var def = sym.definition();
235 var in_use = in_use_ids.has(def.id);
236 if (node instanceof AST_Assign) {
237 if (!in_use || fixed_ids.has(def.id) && fixed_ids.get(def.id) !== node) {
238 const assignee = node.right.transform(tt);
239 if (!in_use && !assignee.has_side_effects(compressor) && !is_used_in_expression(tt)) {
240 return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
241 }
242 return maintain_this_binding(parent, node, assignee);
243 }
244 } else if (!in_use) {
245 return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
246 }
247 }
248 }
249 if (scope !== self) return;
250 var def;
251 if (node.name
252 && (node instanceof AST_ClassExpression
253 && !keep_name(compressor.option("keep_classnames"), (def = node.name.definition()).name)
254 || node instanceof AST_Function
255 && !keep_name(compressor.option("keep_fnames"), (def = node.name.definition()).name))) {
256 // any declarations with same name will overshadow
257 // name of this anonymous function and can therefore
258 // never be used anywhere
259 if (!in_use_ids.has(def.id) || def.orig.length > 1) node.name = null;
260 }
261 if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
262 var trim =
263 !compressor.option("keep_fargs")
264 // Is this an IIFE that won't refer to its name?
265 || parent instanceof AST_Call
266 && parent.expression === node
267 && !node.pinned()
268 && (!node.name || node.name.unreferenced());
269 for (var a = node.argnames, i = a.length; --i >= 0;) {
270 var sym = a[i];
271 if (sym instanceof AST_Expansion) {
272 sym = sym.expression;
273 }
274 if (sym instanceof AST_DefaultAssign) {
275 sym = sym.left;
276 }
277 // Do not drop destructuring arguments.
278 // They constitute a type assertion of sorts
279 if (
280 !(sym instanceof AST_Destructuring)
281 && !in_use_ids.has(sym.definition().id)
282 ) {
283 set_flag(sym, UNUSED);
284 if (trim) {
285 a.pop();
286 }
287 } else {
288 trim = false;
289 }
290 }
291 }
292 if (node instanceof AST_DefClass && node !== self) {
293 const def = node.name.definition();
294 descend(node, this);
295 const keep_class = def.global && !drop_funcs || in_use_ids.has(def.id);
296 if (!keep_class) {
297 const kept = node.drop_side_effect_free(compressor);
298 if (kept == null) {
299 def.eliminated++;
300 return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
301 }
302 return kept;
303 }
304 return node;
305 }
306 if (node instanceof AST_Defun && node !== self) {
307 const def = node.name.definition();
308 const keep = def.global && !drop_funcs || in_use_ids.has(def.id);
309 if (!keep) {
310 def.eliminated++;
311 return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
312 }
313 }
314 if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
315 var drop_block = !(parent instanceof AST_Toplevel) && !(node instanceof AST_Var);
316 // place uninitialized names at the start
317 var body = [], head = [], tail = [];
318 // for unused names whose initialization has
319 // side effects, we can cascade the init. code
320 // into the next one, or next statement.
321 var side_effects = [];
322 node.definitions.forEach(function(def) {
323 if (def.value) def.value = def.value.transform(tt);
324 var is_destructure = def.name instanceof AST_Destructuring;
325 var sym = is_destructure
326 ? new SymbolDef(null, { name: "<destructure>" }) /* fake SymbolDef */
327 : def.name.definition();
328 if (drop_block && sym.global) return tail.push(def);
329 if (!(drop_vars || drop_block)
330 || is_destructure
331 && (def.name.names.length
332 || def.name.is_array
333 || compressor.option("pure_getters") != true)
334 || in_use_ids.has(sym.id)
335 ) {
336 if (def.value && fixed_ids.has(sym.id) && fixed_ids.get(sym.id) !== def) {
337 def.value = def.value.drop_side_effect_free(compressor);
338 }
339 if (def.name instanceof AST_SymbolVar) {
340 var var_defs = var_defs_by_id.get(sym.id);
341 if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
342 if (def.value) {
343 var ref = make_node(AST_SymbolRef, def.name, def.name);
344 sym.references.push(ref);
345 var assign = make_node(AST_Assign, def, {
346 operator: "=",
347 logical: false,
348 left: ref,
349 right: def.value
350 });
351 if (fixed_ids.get(sym.id) === def) {
352 fixed_ids.set(sym.id, assign);
353 }
354 side_effects.push(assign.transform(tt));
355 }
356 remove(var_defs, def);
357 sym.eliminated++;
358 return;
359 }
360 }
361 if (def.value) {
362 if (side_effects.length > 0) {
363 if (tail.length > 0) {
364 side_effects.push(def.value);
365 def.value = make_sequence(def.value, side_effects);
366 } else {
367 body.push(make_node(AST_SimpleStatement, node, {
368 body: make_sequence(node, side_effects)
369 }));
370 }
371 side_effects = [];
372 }
373 tail.push(def);
374 } else {
375 head.push(def);
376 }
377 } else if (sym.orig[0] instanceof AST_SymbolCatch) {
378 var value = def.value && def.value.drop_side_effect_free(compressor);
379 if (value) side_effects.push(value);
380 def.value = null;
381 head.push(def);
382 } else {
383 var value = def.value && def.value.drop_side_effect_free(compressor);
384 if (value) {
385 side_effects.push(value);
386 }
387 sym.eliminated++;
388 }
389 });
390 if (head.length > 0 || tail.length > 0) {
391 node.definitions = head.concat(tail);
392 body.push(node);
393 }
394 if (side_effects.length > 0) {
395 body.push(make_node(AST_SimpleStatement, node, {
396 body: make_sequence(node, side_effects)
397 }));
398 }
399 switch (body.length) {
400 case 0:
401 return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
402 case 1:
403 return body[0];
404 default:
405 return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { body });
406 }
407 }
408 // certain combination of unused name + side effect leads to:
409 // https://github.com/mishoo/UglifyJS2/issues/44
410 // https://github.com/mishoo/UglifyJS2/issues/1830
411 // https://github.com/mishoo/UglifyJS2/issues/1838
412 // that's an invalid AST.
413 // We fix it at this stage by moving the `var` outside the `for`.
414 if (node instanceof AST_For) {
415 descend(node, this);
416 var block;
417 if (node.init instanceof AST_BlockStatement) {
418 block = node.init;
419 node.init = block.body.pop();
420 block.body.push(node);
421 }
422 if (node.init instanceof AST_SimpleStatement) {
423 node.init = node.init.body;
424 } else if (is_empty(node.init)) {
425 node.init = null;
426 }
427 return !block ? node : in_list ? MAP.splice(block.body) : block;
428 }
429 if (node instanceof AST_LabeledStatement
430 && node.body instanceof AST_For
431 ) {
432 descend(node, this);
433 if (node.body instanceof AST_BlockStatement) {
434 var block = node.body;
435 node.body = block.body.pop();
436 block.body.push(node);
437 return in_list ? MAP.splice(block.body) : block;
438 }
439 return node;
440 }
441 if (node instanceof AST_BlockStatement) {
442 descend(node, this);
443 if (in_list && node.body.every(can_be_evicted_from_block)) {
444 return MAP.splice(node.body);
445 }
446 return node;
447 }
448 if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
449 const save_scope = scope;
450 scope = node;
451 descend(node, this);
452 scope = save_scope;
453 return node;
454 }
455 },
456 function after(node, in_list) {
457 if (node instanceof AST_Sequence) {
458 switch (node.expressions.length) {
459 case 0: return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
460 case 1: return node.expressions[0];
461 }
462 }
463 }
464 );
465
466 self.transform(tt);
467
468 function scan_ref_scoped(node, descend) {
469 var node_def;
470 const sym = assign_as_unused(node);
471 if (sym instanceof AST_SymbolRef
472 && !is_ref_of(node.left, AST_SymbolBlockDeclaration)
473 && self.variables.get(sym.name) === (node_def = sym.definition())
474 ) {
475 if (node instanceof AST_Assign) {
476 node.right.walk(tw);
477 if (!node_def.chained && node.left.fixed_value() === node.right) {
478 fixed_ids.set(node_def.id, node);
479 }
480 }
481 return true;
482 }
483 if (node instanceof AST_SymbolRef) {
484 node_def = node.definition();
485 if (!in_use_ids.has(node_def.id)) {
486 in_use_ids.set(node_def.id, node_def);
487 if (node_def.orig[0] instanceof AST_SymbolCatch) {
488 const redef = node_def.scope.is_block_scope()
489 && node_def.scope.get_defun_scope().variables.get(node_def.name);
490 if (redef) in_use_ids.set(redef.id, redef);
491 }
492 }
493 return true;
494 }
495 if (node instanceof AST_Class) {
496 descend();
497 return true;
498 }
499 if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
500 var save_scope = scope;
501 scope = node;
502 descend();
503 scope = save_scope;
504 return true;
505 }
506 }
507});
Note: See TracBrowser for help on using the repository browser.