drop-unused.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544
  1. /***********************************************************************
  2. A JavaScript tokenizer / parser / beautifier / compressor.
  3. https://github.com/mishoo/UglifyJS2
  4. -------------------------------- (C) ---------------------------------
  5. Author: Mihai Bazon
  6. <mihai.bazon@gmail.com>
  7. http://mihai.bazon.net/blog
  8. Distributed under the BSD license:
  9. Copyright 2012 (c) Mihai Bazon <mihai.bazon@gmail.com>
  10. Redistribution and use in source and binary forms, with or without
  11. modification, are permitted provided that the following conditions
  12. are met:
  13. * Redistributions of source code must retain the above
  14. copyright notice, this list of conditions and the following
  15. disclaimer.
  16. * Redistributions in binary form must reproduce the above
  17. copyright notice, this list of conditions and the following
  18. disclaimer in the documentation and/or other materials
  19. provided with the distribution.
  20. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
  21. EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22. IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  23. PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
  24. LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
  25. OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  26. PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  27. PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28. THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
  29. TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
  30. THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  31. SUCH DAMAGE.
  32. ***********************************************************************/
  33. import {
  34. AST_Accessor,
  35. AST_Assign,
  36. AST_BlockStatement,
  37. AST_Call,
  38. AST_Class,
  39. AST_ClassExpression,
  40. AST_ClassStaticBlock,
  41. AST_DefaultAssign,
  42. AST_DefClass,
  43. AST_Definitions,
  44. AST_Defun,
  45. AST_Destructuring,
  46. AST_EmptyStatement,
  47. AST_Expansion,
  48. AST_Export,
  49. AST_For,
  50. AST_ForIn,
  51. AST_Function,
  52. AST_LabeledStatement,
  53. AST_Lambda,
  54. AST_Number,
  55. AST_Scope,
  56. AST_Sequence,
  57. AST_SimpleStatement,
  58. AST_SymbolBlockDeclaration,
  59. AST_SymbolCatch,
  60. AST_SymbolDeclaration,
  61. AST_SymbolFunarg,
  62. AST_SymbolRef,
  63. AST_SymbolVar,
  64. AST_Toplevel,
  65. AST_Unary,
  66. AST_Var,
  67. TreeTransformer,
  68. TreeWalker,
  69. walk,
  70. } from "../ast.js";
  71. import {
  72. keep_name,
  73. make_node,
  74. map_add,
  75. MAP,
  76. remove,
  77. return_false,
  78. } from "../utils/index.js";
  79. import { SymbolDef } from "../scope.js";
  80. import {
  81. WRITE_ONLY,
  82. UNUSED,
  83. has_flag,
  84. set_flag,
  85. } from "./compressor-flags.js";
  86. import {
  87. make_sequence,
  88. maintain_this_binding,
  89. is_empty,
  90. is_ref_of,
  91. can_be_evicted_from_block,
  92. requires_sequence_to_maintain_binding,
  93. } from "./common.js";
  94. const r_keep_assign = /keep_assign/;
  95. /** Drop unused variables from this scope */
  96. AST_Scope.DEFMETHOD("drop_unused", function(compressor) {
  97. if (!compressor.option("unused")) return;
  98. if (compressor.has_directive("use asm")) return;
  99. if (!this.variables) return; // not really a scope (eg: AST_Class)
  100. var self = this;
  101. if (self.pinned()) return;
  102. var drop_funcs = !(self instanceof AST_Toplevel) || compressor.toplevel.funcs;
  103. var drop_vars = !(self instanceof AST_Toplevel) || compressor.toplevel.vars;
  104. const assign_as_unused = r_keep_assign.test(compressor.option("unused")) ? return_false : function(node) {
  105. if (node instanceof AST_Assign
  106. && !node.logical
  107. && (has_flag(node, WRITE_ONLY) || node.operator == "=")
  108. ) {
  109. return node.left;
  110. }
  111. if (node instanceof AST_Unary && has_flag(node, WRITE_ONLY)) {
  112. return node.expression;
  113. }
  114. };
  115. var in_use_ids = new Map();
  116. var fixed_ids = new Map();
  117. if (self instanceof AST_Toplevel && compressor.top_retain) {
  118. self.variables.forEach(function(def) {
  119. if (compressor.top_retain(def)) {
  120. in_use_ids.set(def.id, def);
  121. }
  122. });
  123. }
  124. var var_defs_by_id = new Map();
  125. var initializations = new Map();
  126. // pass 1: find out which symbols are directly used in
  127. // this scope (not in nested scopes).
  128. var scope = this;
  129. var tw = new TreeWalker(function(node, descend) {
  130. if (node instanceof AST_Lambda && node.uses_arguments && !tw.has_directive("use strict")) {
  131. node.argnames.forEach(function(argname) {
  132. if (!(argname instanceof AST_SymbolDeclaration)) return;
  133. var def = argname.definition();
  134. in_use_ids.set(def.id, def);
  135. });
  136. }
  137. if (node === self) return;
  138. if (node instanceof AST_Class && node.has_side_effects(compressor)) {
  139. if (node.is_self_referential()) {
  140. descend();
  141. } else {
  142. node.visit_nondeferred_class_parts(tw);
  143. }
  144. }
  145. if (node instanceof AST_Defun || node instanceof AST_DefClass) {
  146. var node_def = node.name.definition();
  147. const in_export = tw.parent() instanceof AST_Export;
  148. if (in_export || !drop_funcs && scope === self) {
  149. if (node_def.global) {
  150. in_use_ids.set(node_def.id, node_def);
  151. }
  152. }
  153. map_add(initializations, node_def.id, node);
  154. return true; // don't go in nested scopes
  155. }
  156. // In the root scope, we drop things. In inner scopes, we just check for uses.
  157. const in_root_scope = scope === self;
  158. if (node instanceof AST_SymbolFunarg && in_root_scope) {
  159. map_add(var_defs_by_id, node.definition().id, node);
  160. }
  161. if (node instanceof AST_Definitions && in_root_scope) {
  162. const in_export = tw.parent() instanceof AST_Export;
  163. node.definitions.forEach(function(def) {
  164. if (def.name instanceof AST_SymbolVar) {
  165. map_add(var_defs_by_id, def.name.definition().id, def);
  166. }
  167. if (in_export || !drop_vars) {
  168. walk(def.name, node => {
  169. if (node instanceof AST_SymbolDeclaration) {
  170. const def = node.definition();
  171. if (def.global) {
  172. in_use_ids.set(def.id, def);
  173. }
  174. }
  175. });
  176. }
  177. if (def.name instanceof AST_Destructuring) {
  178. def.walk(tw);
  179. }
  180. if (def.name instanceof AST_SymbolDeclaration && def.value) {
  181. var node_def = def.name.definition();
  182. map_add(initializations, node_def.id, def.value);
  183. if (!node_def.chained && def.name.fixed_value() === def.value) {
  184. fixed_ids.set(node_def.id, def);
  185. }
  186. if (def.value.has_side_effects(compressor)) {
  187. def.value.walk(tw);
  188. }
  189. }
  190. });
  191. return true;
  192. }
  193. return scan_ref_scoped(node, descend);
  194. });
  195. self.walk(tw);
  196. // pass 2: for every used symbol we need to walk its
  197. // initialization code to figure out if it uses other
  198. // symbols (that may not be in_use).
  199. tw = new TreeWalker(scan_ref_scoped);
  200. in_use_ids.forEach(function (def) {
  201. var init = initializations.get(def.id);
  202. if (init) init.forEach(function(init) {
  203. init.walk(tw);
  204. });
  205. });
  206. // pass 3: we should drop declarations not in_use
  207. var tt = new TreeTransformer(
  208. function before(node, descend, in_list) {
  209. var parent = tt.parent();
  210. if (drop_vars) {
  211. const sym = assign_as_unused(node);
  212. if (sym instanceof AST_SymbolRef) {
  213. var def = sym.definition();
  214. var in_use = in_use_ids.has(def.id);
  215. if (node instanceof AST_Assign) {
  216. if (!in_use || fixed_ids.has(def.id) && fixed_ids.get(def.id) !== node) {
  217. const assignee = node.right.transform(tt);
  218. if (!in_use && !assignee.has_side_effects(compressor) && !is_used_in_expression(tt)) {
  219. return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
  220. }
  221. return maintain_this_binding(parent, node, assignee);
  222. }
  223. } else if (!in_use) {
  224. return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
  225. }
  226. }
  227. }
  228. if (scope !== self) return;
  229. var def;
  230. if (node.name
  231. && (node instanceof AST_ClassExpression
  232. && !keep_name(compressor.option("keep_classnames"), (def = node.name.definition()).name)
  233. || node instanceof AST_Function
  234. && !keep_name(compressor.option("keep_fnames"), (def = node.name.definition()).name))) {
  235. // any declarations with same name will overshadow
  236. // name of this anonymous function and can therefore
  237. // never be used anywhere
  238. if (!in_use_ids.has(def.id) || def.orig.length > 1) node.name = null;
  239. }
  240. if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) {
  241. var trim =
  242. !compressor.option("keep_fargs")
  243. // Is this an IIFE that won't refer to its name?
  244. || parent instanceof AST_Call
  245. && parent.expression === node
  246. && !node.pinned()
  247. && (!node.name || node.name.unreferenced());
  248. for (var a = node.argnames, i = a.length; --i >= 0;) {
  249. var sym = a[i];
  250. if (sym instanceof AST_Expansion) {
  251. sym = sym.expression;
  252. }
  253. if (sym instanceof AST_DefaultAssign) {
  254. sym = sym.left;
  255. }
  256. // Do not drop destructuring arguments.
  257. // They constitute a type assertion of sorts
  258. if (
  259. !(sym instanceof AST_Destructuring)
  260. && !in_use_ids.has(sym.definition().id)
  261. ) {
  262. set_flag(sym, UNUSED);
  263. if (trim) {
  264. a.pop();
  265. }
  266. } else {
  267. trim = false;
  268. }
  269. }
  270. }
  271. if (node instanceof AST_DefClass && node !== self) {
  272. const def = node.name.definition();
  273. descend(node, this);
  274. const keep_class = def.global && !drop_funcs || in_use_ids.has(def.id);
  275. if (!keep_class) {
  276. const kept = node.drop_side_effect_free(compressor);
  277. if (kept == null) {
  278. def.eliminated++;
  279. return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
  280. }
  281. return kept;
  282. }
  283. return node;
  284. }
  285. if (node instanceof AST_Defun && node !== self) {
  286. const def = node.name.definition();
  287. const keep = def.global && !drop_funcs || in_use_ids.has(def.id);
  288. if (!keep) {
  289. def.eliminated++;
  290. return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
  291. }
  292. }
  293. if (node instanceof AST_Definitions && !(parent instanceof AST_ForIn && parent.init === node)) {
  294. var drop_block = !(parent instanceof AST_Toplevel) && !(node instanceof AST_Var);
  295. // place uninitialized names at the start
  296. var body = [], head = [], tail = [];
  297. // for unused names whose initialization has
  298. // side effects, we can cascade the init. code
  299. // into the next one, or next statement.
  300. var side_effects = [];
  301. node.definitions.forEach(function(def) {
  302. if (def.value) def.value = def.value.transform(tt);
  303. var is_destructure = def.name instanceof AST_Destructuring;
  304. var sym = is_destructure
  305. ? new SymbolDef(null, { name: "<destructure>" }) /* fake SymbolDef */
  306. : def.name.definition();
  307. if (drop_block && sym.global) return tail.push(def);
  308. if (!(drop_vars || drop_block)
  309. || is_destructure
  310. && (def.name.names.length
  311. || def.name.is_array
  312. || compressor.option("pure_getters") != true)
  313. || in_use_ids.has(sym.id)
  314. ) {
  315. if (def.value && fixed_ids.has(sym.id) && fixed_ids.get(sym.id) !== def) {
  316. def.value = def.value.drop_side_effect_free(compressor);
  317. }
  318. if (def.name instanceof AST_SymbolVar) {
  319. var var_defs = var_defs_by_id.get(sym.id);
  320. if (var_defs.length > 1 && (!def.value || sym.orig.indexOf(def.name) > sym.eliminated)) {
  321. if (def.value) {
  322. var ref = make_node(AST_SymbolRef, def.name, def.name);
  323. sym.references.push(ref);
  324. var assign = make_node(AST_Assign, def, {
  325. operator: "=",
  326. logical: false,
  327. left: ref,
  328. right: def.value
  329. });
  330. if (fixed_ids.get(sym.id) === def) {
  331. fixed_ids.set(sym.id, assign);
  332. }
  333. side_effects.push(assign.transform(tt));
  334. }
  335. remove(var_defs, def);
  336. sym.eliminated++;
  337. return;
  338. }
  339. }
  340. if (def.value) {
  341. if (side_effects.length > 0) {
  342. if (tail.length > 0) {
  343. side_effects.push(def.value);
  344. def.value = make_sequence(def.value, side_effects);
  345. } else {
  346. body.push(make_node(AST_SimpleStatement, node, {
  347. body: make_sequence(node, side_effects)
  348. }));
  349. }
  350. side_effects = [];
  351. }
  352. tail.push(def);
  353. } else {
  354. head.push(def);
  355. }
  356. } else if (sym.orig[0] instanceof AST_SymbolCatch) {
  357. var value = def.value && def.value.drop_side_effect_free(compressor);
  358. if (value) side_effects.push(value);
  359. def.value = null;
  360. head.push(def);
  361. } else {
  362. var value = def.value && def.value.drop_side_effect_free(compressor);
  363. if (value) {
  364. side_effects.push(value);
  365. }
  366. sym.eliminated++;
  367. }
  368. });
  369. if (head.length > 0 || tail.length > 0) {
  370. node.definitions = head.concat(tail);
  371. body.push(node);
  372. }
  373. if (side_effects.length > 0) {
  374. body.push(make_node(AST_SimpleStatement, node, {
  375. body: make_sequence(node, side_effects)
  376. }));
  377. }
  378. switch (body.length) {
  379. case 0:
  380. return in_list ? MAP.skip : make_node(AST_EmptyStatement, node);
  381. case 1:
  382. return body[0];
  383. default:
  384. return in_list ? MAP.splice(body) : make_node(AST_BlockStatement, node, { body });
  385. }
  386. }
  387. // certain combination of unused name + side effect leads to:
  388. // https://github.com/mishoo/UglifyJS2/issues/44
  389. // https://github.com/mishoo/UglifyJS2/issues/1830
  390. // https://github.com/mishoo/UglifyJS2/issues/1838
  391. // that's an invalid AST.
  392. // We fix it at this stage by moving the `var` outside the `for`.
  393. if (node instanceof AST_For) {
  394. descend(node, this);
  395. var block;
  396. if (node.init instanceof AST_BlockStatement) {
  397. block = node.init;
  398. node.init = block.body.pop();
  399. block.body.push(node);
  400. }
  401. if (node.init instanceof AST_SimpleStatement) {
  402. node.init = node.init.body;
  403. } else if (is_empty(node.init)) {
  404. node.init = null;
  405. }
  406. return !block ? node : in_list ? MAP.splice(block.body) : block;
  407. }
  408. if (node instanceof AST_LabeledStatement
  409. && node.body instanceof AST_For
  410. ) {
  411. descend(node, this);
  412. if (node.body instanceof AST_BlockStatement) {
  413. var block = node.body;
  414. node.body = block.body.pop();
  415. block.body.push(node);
  416. return in_list ? MAP.splice(block.body) : block;
  417. }
  418. return node;
  419. }
  420. if (node instanceof AST_BlockStatement) {
  421. descend(node, this);
  422. if (in_list && node.body.every(can_be_evicted_from_block)) {
  423. return MAP.splice(node.body);
  424. }
  425. return node;
  426. }
  427. if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
  428. const save_scope = scope;
  429. scope = node;
  430. descend(node, this);
  431. scope = save_scope;
  432. return node;
  433. }
  434. },
  435. function after(node, in_list) {
  436. if (node instanceof AST_Sequence) {
  437. switch (node.expressions.length) {
  438. case 0: return in_list ? MAP.skip : make_node(AST_Number, node, { value: 0 });
  439. case 1: return node.expressions[0];
  440. }
  441. }
  442. }
  443. );
  444. self.transform(tt);
  445. function scan_ref_scoped(node, descend) {
  446. var node_def;
  447. const sym = assign_as_unused(node);
  448. if (sym instanceof AST_SymbolRef
  449. && !is_ref_of(node.left, AST_SymbolBlockDeclaration)
  450. && self.variables.get(sym.name) === (node_def = sym.definition())
  451. ) {
  452. if (node instanceof AST_Assign) {
  453. node.right.walk(tw);
  454. if (!node_def.chained && node.left.fixed_value() === node.right) {
  455. fixed_ids.set(node_def.id, node);
  456. }
  457. }
  458. return true;
  459. }
  460. if (node instanceof AST_SymbolRef) {
  461. node_def = node.definition();
  462. if (!in_use_ids.has(node_def.id)) {
  463. in_use_ids.set(node_def.id, node_def);
  464. if (node_def.orig[0] instanceof AST_SymbolCatch) {
  465. const redef = node_def.scope.is_block_scope()
  466. && node_def.scope.get_defun_scope().variables.get(node_def.name);
  467. if (redef) in_use_ids.set(redef.id, redef);
  468. }
  469. }
  470. return true;
  471. }
  472. if (node instanceof AST_Class) {
  473. descend();
  474. return true;
  475. }
  476. if (node instanceof AST_Scope && !(node instanceof AST_ClassStaticBlock)) {
  477. var save_scope = scope;
  478. scope = node;
  479. descend();
  480. scope = save_scope;
  481. return true;
  482. }
  483. }
  484. });
  485. /**
  486. * Check if a node may be used by the expression it's in
  487. * void (0, 1, {node}, 2) -> false
  488. * console.log(0, {node}) -> true
  489. */
  490. function is_used_in_expression(tw) {
  491. for (let p = -1, node, parent; node = tw.parent(p), parent = tw.parent(p + 1); p++) {
  492. if (parent instanceof AST_Sequence) {
  493. const nth_expression = parent.expressions.indexOf(node);
  494. if (nth_expression !== parent.expressions.length - 1) {
  495. // Detect (0, x.noThis)() constructs
  496. const grandparent = tw.parent(p + 2);
  497. if (
  498. parent.expressions.length > 2
  499. || parent.expressions.length === 1
  500. || !requires_sequence_to_maintain_binding(grandparent, parent, parent.expressions[1])
  501. ) {
  502. return false;
  503. }
  504. return true;
  505. } else {
  506. continue;
  507. }
  508. }
  509. if (parent instanceof AST_Unary) {
  510. const op = parent.operator;
  511. if (op === "void") {
  512. return false;
  513. }
  514. if (op === "typeof" || op === "+" || op === "-" || op === "!" || op === "~") {
  515. continue;
  516. }
  517. }
  518. return true;
  519. }
  520. return true;
  521. }