drop-side-effect-free.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  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_Array,
  36. AST_Arrow,
  37. AST_Assign,
  38. AST_Binary,
  39. AST_Call,
  40. AST_Chain,
  41. AST_Class,
  42. AST_ClassStaticBlock,
  43. AST_ClassProperty,
  44. AST_ConciseMethod,
  45. AST_Conditional,
  46. AST_Constant,
  47. AST_DefClass,
  48. AST_Dot,
  49. AST_Expansion,
  50. AST_Function,
  51. AST_Node,
  52. AST_Number,
  53. AST_Object,
  54. AST_ObjectGetter,
  55. AST_ObjectKeyVal,
  56. AST_ObjectProperty,
  57. AST_ObjectSetter,
  58. AST_PropAccess,
  59. AST_Scope,
  60. AST_Sequence,
  61. AST_SimpleStatement,
  62. AST_Sub,
  63. AST_SymbolRef,
  64. AST_TemplateSegment,
  65. AST_TemplateString,
  66. AST_This,
  67. AST_Unary,
  68. } from "../ast.js";
  69. import { make_node, return_null, return_this } from "../utils/index.js";
  70. import { first_in_statement } from "../utils/first_in_statement.js";
  71. import { pure_prop_access_globals } from "./native-objects.js";
  72. import { lazy_op, unary_side_effects, is_nullish_shortcircuited } from "./inference.js";
  73. import { WRITE_ONLY, set_flag, clear_flag } from "./compressor-flags.js";
  74. import { make_sequence, is_func_expr, is_iife_call } from "./common.js";
  75. // AST_Node#drop_side_effect_free() gets called when we don't care about the value,
  76. // only about side effects. We'll be defining this method for each node type in this module
  77. //
  78. // Examples:
  79. // foo++ -> foo++
  80. // 1 + func() -> func()
  81. // 10 -> (nothing)
  82. // knownPureFunc(foo++) -> foo++
  83. function def_drop_side_effect_free(node, func) {
  84. node.DEFMETHOD("drop_side_effect_free", func);
  85. }
  86. // Drop side-effect-free elements from an array of expressions.
  87. // Returns an array of expressions with side-effects or null
  88. // if all elements were dropped. Note: original array may be
  89. // returned if nothing changed.
  90. function trim(nodes, compressor, first_in_statement) {
  91. var len = nodes.length;
  92. if (!len) return null;
  93. var ret = [], changed = false;
  94. for (var i = 0; i < len; i++) {
  95. var node = nodes[i].drop_side_effect_free(compressor, first_in_statement);
  96. changed |= node !== nodes[i];
  97. if (node) {
  98. ret.push(node);
  99. first_in_statement = false;
  100. }
  101. }
  102. return changed ? ret.length ? ret : null : nodes;
  103. }
  104. def_drop_side_effect_free(AST_Node, return_this);
  105. def_drop_side_effect_free(AST_Constant, return_null);
  106. def_drop_side_effect_free(AST_This, return_null);
  107. def_drop_side_effect_free(AST_Call, function (compressor, first_in_statement) {
  108. if (is_nullish_shortcircuited(this, compressor)) {
  109. return this.expression.drop_side_effect_free(compressor, first_in_statement);
  110. }
  111. if (!this.is_callee_pure(compressor)) {
  112. if (this.expression.is_call_pure(compressor)) {
  113. var exprs = this.args.slice();
  114. exprs.unshift(this.expression.expression);
  115. exprs = trim(exprs, compressor, first_in_statement);
  116. return exprs && make_sequence(this, exprs);
  117. }
  118. if (is_func_expr(this.expression)
  119. && (!this.expression.name || !this.expression.name.definition().references.length)) {
  120. var node = this.clone();
  121. node.expression.process_expression(false, compressor);
  122. return node;
  123. }
  124. return this;
  125. }
  126. var args = trim(this.args, compressor, first_in_statement);
  127. return args && make_sequence(this, args);
  128. });
  129. def_drop_side_effect_free(AST_Accessor, return_null);
  130. def_drop_side_effect_free(AST_Function, return_null);
  131. def_drop_side_effect_free(AST_Arrow, return_null);
  132. def_drop_side_effect_free(AST_Class, function (compressor) {
  133. const with_effects = [];
  134. if (this.is_self_referential() && this.has_side_effects(compressor)) {
  135. return this;
  136. }
  137. const trimmed_extends = this.extends && this.extends.drop_side_effect_free(compressor);
  138. if (trimmed_extends) with_effects.push(trimmed_extends);
  139. for (const prop of this.properties) {
  140. if (prop instanceof AST_ClassStaticBlock) {
  141. if (prop.has_side_effects(compressor)) {
  142. return this; // Be cautious about these
  143. }
  144. } else {
  145. const trimmed_prop = prop.drop_side_effect_free(compressor);
  146. if (trimmed_prop) with_effects.push(trimmed_prop);
  147. }
  148. }
  149. if (!with_effects.length)
  150. return null;
  151. const exprs = make_sequence(this, with_effects);
  152. if (this instanceof AST_DefClass) {
  153. // We want a statement
  154. return make_node(AST_SimpleStatement, this, { body: exprs });
  155. } else {
  156. return exprs;
  157. }
  158. });
  159. def_drop_side_effect_free(AST_ClassProperty, function (compressor) {
  160. const key = this.computed_key() && this.key.drop_side_effect_free(compressor);
  161. const value = this.static && this.value
  162. && this.value.drop_side_effect_free(compressor);
  163. if (key && value)
  164. return make_sequence(this, [key, value]);
  165. return key || value || null;
  166. });
  167. def_drop_side_effect_free(AST_Binary, function (compressor, first_in_statement) {
  168. var right = this.right.drop_side_effect_free(compressor);
  169. if (!right)
  170. return this.left.drop_side_effect_free(compressor, first_in_statement);
  171. if (lazy_op.has(this.operator)) {
  172. if (right === this.right)
  173. return this;
  174. var node = this.clone();
  175. node.right = right;
  176. return node;
  177. } else {
  178. var left = this.left.drop_side_effect_free(compressor, first_in_statement);
  179. if (!left)
  180. return this.right.drop_side_effect_free(compressor, first_in_statement);
  181. return make_sequence(this, [left, right]);
  182. }
  183. });
  184. def_drop_side_effect_free(AST_Assign, function (compressor) {
  185. if (this.logical)
  186. return this;
  187. var left = this.left;
  188. if (left.has_side_effects(compressor)
  189. || compressor.has_directive("use strict")
  190. && left instanceof AST_PropAccess
  191. && left.expression.is_constant()) {
  192. return this;
  193. }
  194. set_flag(this, WRITE_ONLY);
  195. while (left instanceof AST_PropAccess) {
  196. left = left.expression;
  197. }
  198. if (left.is_constant_expression(compressor.find_parent(AST_Scope))) {
  199. return this.right.drop_side_effect_free(compressor);
  200. }
  201. return this;
  202. });
  203. def_drop_side_effect_free(AST_Conditional, function (compressor) {
  204. var consequent = this.consequent.drop_side_effect_free(compressor);
  205. var alternative = this.alternative.drop_side_effect_free(compressor);
  206. if (consequent === this.consequent && alternative === this.alternative)
  207. return this;
  208. if (!consequent)
  209. return alternative ? make_node(AST_Binary, this, {
  210. operator: "||",
  211. left: this.condition,
  212. right: alternative
  213. }) : this.condition.drop_side_effect_free(compressor);
  214. if (!alternative)
  215. return make_node(AST_Binary, this, {
  216. operator: "&&",
  217. left: this.condition,
  218. right: consequent
  219. });
  220. var node = this.clone();
  221. node.consequent = consequent;
  222. node.alternative = alternative;
  223. return node;
  224. });
  225. def_drop_side_effect_free(AST_Unary, function (compressor, first_in_statement) {
  226. if (unary_side_effects.has(this.operator)) {
  227. if (!this.expression.has_side_effects(compressor)) {
  228. set_flag(this, WRITE_ONLY);
  229. } else {
  230. clear_flag(this, WRITE_ONLY);
  231. }
  232. return this;
  233. }
  234. if (this.operator == "typeof" && this.expression instanceof AST_SymbolRef)
  235. return null;
  236. var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
  237. if (first_in_statement && expression && is_iife_call(expression)) {
  238. if (expression === this.expression && this.operator == "!")
  239. return this;
  240. return expression.negate(compressor, first_in_statement);
  241. }
  242. return expression;
  243. });
  244. def_drop_side_effect_free(AST_SymbolRef, function (compressor) {
  245. const safe_access = this.is_declared(compressor)
  246. || pure_prop_access_globals.has(this.name);
  247. return safe_access ? null : this;
  248. });
  249. def_drop_side_effect_free(AST_Object, function (compressor, first_in_statement) {
  250. var values = trim(this.properties, compressor, first_in_statement);
  251. return values && make_sequence(this, values);
  252. });
  253. def_drop_side_effect_free(AST_ObjectProperty, function (compressor, first_in_statement) {
  254. const computed_key = this instanceof AST_ObjectKeyVal && this.key instanceof AST_Node;
  255. const key = computed_key && this.key.drop_side_effect_free(compressor, first_in_statement);
  256. const value = this.value && this.value.drop_side_effect_free(compressor, first_in_statement);
  257. if (key && value) {
  258. return make_sequence(this, [key, value]);
  259. }
  260. return key || value;
  261. });
  262. def_drop_side_effect_free(AST_ConciseMethod, function () {
  263. return this.computed_key() ? this.key : null;
  264. });
  265. def_drop_side_effect_free(AST_ObjectGetter, function () {
  266. return this.computed_key() ? this.key : null;
  267. });
  268. def_drop_side_effect_free(AST_ObjectSetter, function () {
  269. return this.computed_key() ? this.key : null;
  270. });
  271. def_drop_side_effect_free(AST_Array, function (compressor, first_in_statement) {
  272. var values = trim(this.elements, compressor, first_in_statement);
  273. return values && make_sequence(this, values);
  274. });
  275. def_drop_side_effect_free(AST_Dot, function (compressor, first_in_statement) {
  276. if (is_nullish_shortcircuited(this, compressor)) {
  277. return this.expression.drop_side_effect_free(compressor, first_in_statement);
  278. }
  279. if (!this.optional && this.expression.may_throw_on_access(compressor)) {
  280. return this;
  281. }
  282. return this.expression.drop_side_effect_free(compressor, first_in_statement);
  283. });
  284. def_drop_side_effect_free(AST_Sub, function (compressor, first_in_statement) {
  285. if (is_nullish_shortcircuited(this, compressor)) {
  286. return this.expression.drop_side_effect_free(compressor, first_in_statement);
  287. }
  288. if (!this.optional && this.expression.may_throw_on_access(compressor)) {
  289. return this;
  290. }
  291. var property = this.property.drop_side_effect_free(compressor);
  292. if (property && this.optional) return this;
  293. var expression = this.expression.drop_side_effect_free(compressor, first_in_statement);
  294. if (expression && property) return make_sequence(this, [expression, property]);
  295. return expression || property;
  296. });
  297. def_drop_side_effect_free(AST_Chain, function (compressor, first_in_statement) {
  298. return this.expression.drop_side_effect_free(compressor, first_in_statement);
  299. });
  300. def_drop_side_effect_free(AST_Sequence, function (compressor) {
  301. var last = this.tail_node();
  302. var expr = last.drop_side_effect_free(compressor);
  303. if (expr === last)
  304. return this;
  305. var expressions = this.expressions.slice(0, -1);
  306. if (expr)
  307. expressions.push(expr);
  308. if (!expressions.length) {
  309. return make_node(AST_Number, this, { value: 0 });
  310. }
  311. return make_sequence(this, expressions);
  312. });
  313. def_drop_side_effect_free(AST_Expansion, function (compressor, first_in_statement) {
  314. return this.expression.drop_side_effect_free(compressor, first_in_statement);
  315. });
  316. def_drop_side_effect_free(AST_TemplateSegment, return_null);
  317. def_drop_side_effect_free(AST_TemplateString, function (compressor) {
  318. var values = trim(this.segments, compressor, first_in_statement);
  319. return values && make_sequence(this, values);
  320. });