1 | "use strict";
|
2 |
|
3 | const some = require("lodash.some");
|
4 |
|
5 | const _require = require("babel-helper-mark-eval-scopes"),
|
6 | markEvalScopes = _require.markEvalScopes,
|
7 | hasEval = _require.hasEval;
|
8 |
|
9 | const removeUseStrict = require("./remove-use-strict");
|
10 |
|
11 | const evaluate = require("babel-helper-evaluate-path");
|
12 |
|
13 | function evaluateTruthy(path) {
|
14 | const res = evaluate(path);
|
15 | if (res.confident) return !!res.value;
|
16 | }
|
17 |
|
18 | function prevSiblings(path) {
|
19 | const parentPath = path.parentPath;
|
20 | const siblings = [];
|
21 | let key = parentPath.key;
|
22 |
|
23 | while ((path = parentPath.getSibling(--key)).type) {
|
24 | siblings.push(path);
|
25 | }
|
26 |
|
27 | return siblings;
|
28 | }
|
29 |
|
30 | function forEachAncestor(path, callback) {
|
31 | while (path = path.parentPath) {
|
32 | callback(path);
|
33 | }
|
34 | }
|
35 |
|
36 | module.exports = ({
|
37 | types: t,
|
38 | traverse
|
39 | }) => {
|
40 | const removeOrVoid = require("babel-helper-remove-or-void")(t);
|
41 |
|
42 | const shouldRevisit = Symbol("shouldRevisit");
|
43 |
|
44 |
|
45 |
|
46 | const markForRemoval = Symbol("markForRemoval");
|
47 | const main = {
|
48 |
|
49 | ExpressionStatement(path) {
|
50 | if (path.get("expression").isPure()) {
|
51 | removeOrVoid(path);
|
52 | }
|
53 | },
|
54 |
|
55 | Function: {
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 | exit(path) {
|
62 |
|
63 | if (!this.optimizeRawSize) {
|
64 | return;
|
65 | }
|
66 |
|
67 | const node = path.node,
|
68 | scope = path.scope;
|
69 | const seen = new Set();
|
70 | const declars = [];
|
71 | const mutations = [];
|
72 |
|
73 | for (const name in scope.bindings) {
|
74 | const binding = scope.bindings[name];
|
75 |
|
76 | if (!binding.path.isVariableDeclarator()) {
|
77 | continue;
|
78 | }
|
79 |
|
80 | const declarPath = binding.path.parentPath;
|
81 |
|
82 | if (seen.has(declarPath)) {
|
83 | continue;
|
84 | }
|
85 |
|
86 | seen.add(declarPath);
|
87 |
|
88 | if (declarPath.parentPath.isForInStatement()) {
|
89 | continue;
|
90 | }
|
91 |
|
92 | if (declarPath.parentPath.parentPath.isFunction()) {
|
93 | continue;
|
94 | }
|
95 |
|
96 | if (!declarPath.node || !declarPath.node.declarations) {
|
97 | continue;
|
98 | }
|
99 |
|
100 | const assignmentSequence = [];
|
101 | var _iteratorNormalCompletion = true;
|
102 | var _didIteratorError = false;
|
103 | var _iteratorError = undefined;
|
104 |
|
105 | try {
|
106 | for (var _iterator = declarPath.node.declarations[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
107 | const declar = _step.value;
|
108 | declars.push(declar);
|
109 |
|
110 | if (declar.init) {
|
111 | assignmentSequence.push(t.assignmentExpression("=", declar.id, declar.init));
|
112 | mutations.push(() => {
|
113 | declar.init = null;
|
114 | });
|
115 | }
|
116 | }
|
117 | } catch (err) {
|
118 | _didIteratorError = true;
|
119 | _iteratorError = err;
|
120 | } finally {
|
121 | try {
|
122 | if (!_iteratorNormalCompletion && _iterator.return != null) {
|
123 | _iterator.return();
|
124 | }
|
125 | } finally {
|
126 | if (_didIteratorError) {
|
127 | throw _iteratorError;
|
128 | }
|
129 | }
|
130 | }
|
131 |
|
132 | if (assignmentSequence.length) {
|
133 | mutations.push(() => declarPath.replaceWith(t.sequenceExpression(assignmentSequence)));
|
134 | } else {
|
135 | mutations.push(() => removeOrVoid(declarPath));
|
136 | }
|
137 | }
|
138 |
|
139 | if (declars.length) {
|
140 | mutations.forEach(f => f());
|
141 | var _iteratorNormalCompletion2 = true;
|
142 | var _didIteratorError2 = false;
|
143 | var _iteratorError2 = undefined;
|
144 |
|
145 | try {
|
146 | for (var _iterator2 = node.body.body[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
|
147 | const statement = _step2.value;
|
148 |
|
149 | if (t.isVariableDeclaration(statement)) {
|
150 | statement.declarations.push(...declars);
|
151 | return;
|
152 | }
|
153 | }
|
154 | } catch (err) {
|
155 | _didIteratorError2 = true;
|
156 | _iteratorError2 = err;
|
157 | } finally {
|
158 | try {
|
159 | if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
|
160 | _iterator2.return();
|
161 | }
|
162 | } finally {
|
163 | if (_didIteratorError2) {
|
164 | throw _iteratorError2;
|
165 | }
|
166 | }
|
167 | }
|
168 |
|
169 | const varDecl = t.variableDeclaration("var", declars);
|
170 | node.body.body.unshift(varDecl);
|
171 | }
|
172 | }
|
173 |
|
174 | },
|
175 |
|
176 | Scope: {
|
177 | exit(path) {
|
178 | if (path.node[shouldRevisit]) {
|
179 | delete path.node[shouldRevisit];
|
180 | path.visit();
|
181 | }
|
182 | },
|
183 |
|
184 | enter(path) {
|
185 | if (path.isProgram()) {
|
186 | return;
|
187 | }
|
188 |
|
189 | if (hasEval(path.scope)) {
|
190 | return;
|
191 | }
|
192 |
|
193 | const scope = path.scope;
|
194 |
|
195 |
|
196 | const canRemoveParams = path.isFunction() && path.node.kind !== "set";
|
197 | const paramsList = canRemoveParams ? path.get("params") : [];
|
198 |
|
199 | for (let i = paramsList.length - 1; i >= 0; i--) {
|
200 | const param = paramsList[i];
|
201 |
|
202 | if (param.isIdentifier()) {
|
203 | const binding = scope.bindings[param.node.name];
|
204 | if (!binding) continue;
|
205 |
|
206 | if (binding.referenced) {
|
207 |
|
208 |
|
209 | break;
|
210 | }
|
211 |
|
212 | binding[markForRemoval] = true;
|
213 | continue;
|
214 | } else if (param.isAssignmentPattern()) {
|
215 | const left = param.get("left");
|
216 | const right = param.get("right");
|
217 |
|
218 | if (left.isIdentifier() && right.isPure()) {
|
219 | const binding = scope.bindings[left.node.name];
|
220 |
|
221 | if (binding.referenced) {
|
222 |
|
223 |
|
224 | break;
|
225 | }
|
226 |
|
227 | binding[markForRemoval] = true;
|
228 | continue;
|
229 | }
|
230 | }
|
231 |
|
232 |
|
233 |
|
234 | break;
|
235 | }
|
236 |
|
237 | for (const name in scope.bindings) {
|
238 | const binding = scope.bindings[name];
|
239 |
|
240 | if (!binding.referenced && binding.kind !== "module") {
|
241 | if (binding.kind === "param" && (this.keepFnArgs || !binding[markForRemoval])) {
|
242 | continue;
|
243 | } else if (binding.path.isVariableDeclarator()) {
|
244 | const declaration = binding.path.parentPath;
|
245 | const maybeBlockParent = declaration.parentPath;
|
246 |
|
247 | if (maybeBlockParent && maybeBlockParent.isForXStatement({
|
248 | left: declaration.node
|
249 | })) {
|
250 |
|
251 | continue;
|
252 | }
|
253 | } else if (!scope.isPure(binding.path.node)) {
|
254 |
|
255 | continue;
|
256 | } else if (binding.path.isFunctionExpression() || binding.path.isClassExpression()) {
|
257 |
|
258 | continue;
|
259 | } else if (
|
260 |
|
261 |
|
262 | binding.path.isClassDeclaration() && binding.path === scope.path) {
|
263 | continue;
|
264 | }
|
265 |
|
266 | const mutations = [];
|
267 | let bail = false;
|
268 |
|
269 | binding.constantViolations.forEach(p => {
|
270 | if (bail || p === binding.path) {
|
271 | return;
|
272 | }
|
273 |
|
274 | if (!p.parentPath.isExpressionStatement()) {
|
275 | bail = true;
|
276 | }
|
277 |
|
278 | if (p.isAssignmentExpression() && !p.get("right").isPure()) {
|
279 | mutations.push(() => p.replaceWith(p.get("right")));
|
280 | } else {
|
281 | mutations.push(() => removeOrVoid(p));
|
282 | }
|
283 | });
|
284 |
|
285 | if (bail) {
|
286 | continue;
|
287 | }
|
288 |
|
289 | if (binding.path.isVariableDeclarator()) {
|
290 | if (!binding.path.get("id").isIdentifier()) {
|
291 |
|
292 | continue;
|
293 | }
|
294 |
|
295 |
|
296 |
|
297 |
|
298 | if (binding.path.node.init && !scope.isPure(binding.path.node.init) && binding.path.parentPath.node.declarations) {
|
299 |
|
300 | if (binding.path.parentPath.node.declarations.length !== 1) {
|
301 | continue;
|
302 | }
|
303 |
|
304 | binding.path.parentPath.replaceWith(binding.path.node.init);
|
305 | } else {
|
306 | updateReferences(binding.path, this);
|
307 | removeOrVoid(binding.path);
|
308 | }
|
309 | } else {
|
310 | updateReferences(binding.path, this);
|
311 | removeOrVoid(binding.path);
|
312 | }
|
313 |
|
314 | mutations.forEach(f => f());
|
315 | scope.removeBinding(name);
|
316 | } else if (binding.constant) {
|
317 | if (binding.path.isFunctionDeclaration() || binding.path.isVariableDeclarator() && binding.path.get("init").isFunction()) {
|
318 | const fun = binding.path.isFunctionDeclaration() ? binding.path : binding.path.get("init");
|
319 | let allInside = true;
|
320 | var _iteratorNormalCompletion3 = true;
|
321 | var _didIteratorError3 = false;
|
322 | var _iteratorError3 = undefined;
|
323 |
|
324 | try {
|
325 | for (var _iterator3 = binding.referencePaths[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
|
326 | const ref = _step3.value;
|
327 |
|
328 | if (!ref.find(p => p.node === fun.node)) {
|
329 | allInside = false;
|
330 | break;
|
331 | }
|
332 | }
|
333 | } catch (err) {
|
334 | _didIteratorError3 = true;
|
335 | _iteratorError3 = err;
|
336 | } finally {
|
337 | try {
|
338 | if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
|
339 | _iterator3.return();
|
340 | }
|
341 | } finally {
|
342 | if (_didIteratorError3) {
|
343 | throw _iteratorError3;
|
344 | }
|
345 | }
|
346 | }
|
347 |
|
348 | if (allInside) {
|
349 | scope.removeBinding(name);
|
350 | updateReferences(binding.path, this);
|
351 | removeOrVoid(binding.path);
|
352 | continue;
|
353 | }
|
354 | }
|
355 |
|
356 | if (binding.references === 1 && binding.kind !== "param" && binding.kind !== "module" && binding.constant) {
|
357 | let replacement = binding.path.node;
|
358 | let replacementPath = binding.path;
|
359 | let isReferencedBefore = false;
|
360 | const refPath = binding.referencePaths[0];
|
361 |
|
362 | if (t.isVariableDeclarator(replacement)) {
|
363 | const _prevSiblings = prevSiblings(replacementPath);
|
364 |
|
365 |
|
366 | forEachAncestor(refPath, ancestor => {
|
367 | if (_prevSiblings.indexOf(ancestor) > -1) {
|
368 | isReferencedBefore = true;
|
369 | }
|
370 | });
|
371 |
|
372 |
|
373 |
|
374 | if (isReferencedBefore && refPath.scope !== binding.scope) {
|
375 | continue;
|
376 | }
|
377 |
|
378 |
|
379 |
|
380 | replacement = isReferencedBefore ? t.unaryExpression("void", t.numericLiteral(0), true) : replacement.init;
|
381 |
|
382 |
|
383 | if (!replacementPath.get("id").isIdentifier()) {
|
384 | continue;
|
385 | }
|
386 |
|
387 | replacementPath = replacementPath.get("init");
|
388 | }
|
389 |
|
390 | if (!replacement) {
|
391 | continue;
|
392 | }
|
393 |
|
394 | if (!scope.isPure(replacement, true) && !isReferencedBefore) {
|
395 | continue;
|
396 | }
|
397 |
|
398 | let bail = false;
|
399 |
|
400 | if (replacementPath.isIdentifier()) {
|
401 | const binding = scope.getBinding(replacement.name);
|
402 |
|
403 |
|
404 |
|
405 |
|
406 | bail = !(binding && refPath.scope.getBinding(replacement.name) === binding && binding.constantViolations.length === 0);
|
407 | } else {
|
408 | replacementPath.traverse({
|
409 | Function(path) {
|
410 | path.skip();
|
411 | },
|
412 |
|
413 | ReferencedIdentifier({
|
414 | node
|
415 | }) {
|
416 | if (bail) {
|
417 | return;
|
418 | }
|
419 |
|
420 | const binding = scope.getBinding(node.name);
|
421 |
|
422 | if (binding && refPath.scope.getBinding(node.name) === binding) {
|
423 | bail = binding.constantViolations.length > 0;
|
424 | }
|
425 | }
|
426 |
|
427 | });
|
428 | }
|
429 |
|
430 | if (bail) {
|
431 | continue;
|
432 | }
|
433 |
|
434 | let parent = binding.path.parent;
|
435 |
|
436 | if (t.isVariableDeclaration(parent)) {
|
437 | parent = binding.path.parentPath.parent;
|
438 | }
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 | let mayLoop = false;
|
446 | const sharesRoot = refPath.find(({
|
447 | node
|
448 | }) => {
|
449 | if (!mayLoop) {
|
450 | mayLoop = t.isWhileStatement(node) || t.isFor(node) || t.isFunction(node);
|
451 | }
|
452 |
|
453 | return node === parent;
|
454 | });
|
455 |
|
456 | const isObj = n => t.isFunction(n) || t.isObjectExpression(n) || t.isArrayExpression(n);
|
457 |
|
458 | const isReplacementObj = isObj(replacement) || some(replacement, isObj);
|
459 |
|
460 | if (!sharesRoot || isReplacementObj && mayLoop) {
|
461 | continue;
|
462 | }
|
463 |
|
464 |
|
465 |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 | let inExpression = replacementPath.isBinaryExpression({
|
472 | operator: "in"
|
473 | });
|
474 |
|
475 | if (!inExpression) {
|
476 | replacementPath.traverse({
|
477 | Function(path) {
|
478 | path.skip();
|
479 | },
|
480 |
|
481 | BinaryExpression(path) {
|
482 | if (path.node.operator === "in") {
|
483 | inExpression = true;
|
484 | path.stop();
|
485 | }
|
486 | }
|
487 |
|
488 | });
|
489 | }
|
490 |
|
491 | if (inExpression) {
|
492 | continue;
|
493 | }
|
494 |
|
495 | const replaced = replace(binding.referencePaths[0], {
|
496 | binding,
|
497 | scope,
|
498 | replacement,
|
499 | replacementPath
|
500 | });
|
501 |
|
502 | if (replaced) {
|
503 | scope.removeBinding(name);
|
504 |
|
505 | if (binding.path.node) {
|
506 | removeOrVoid(binding.path);
|
507 | }
|
508 | }
|
509 | }
|
510 | }
|
511 | }
|
512 |
|
513 | }
|
514 |
|
515 | },
|
516 |
|
517 |
|
518 | BlockStatement(path) {
|
519 | const paths = path.get("body");
|
520 | let purge = false;
|
521 |
|
522 | for (let i = 0; i < paths.length; i++) {
|
523 | const p = paths[i];
|
524 |
|
525 | if (!purge && p.isCompletionStatement()) {
|
526 | purge = true;
|
527 | continue;
|
528 | }
|
529 |
|
530 | if (purge && !canExistAfterCompletion(p)) {
|
531 | removeOrVoid(p);
|
532 | }
|
533 | }
|
534 | },
|
535 |
|
536 |
|
537 |
|
538 | ReturnStatement(path) {
|
539 | const node = path.node;
|
540 |
|
541 | if (!path.inList) {
|
542 | return;
|
543 | }
|
544 |
|
545 |
|
546 | if (path.container.length - 1 !== path.key && !canExistAfterCompletion(path.getSibling(path.key + 1)) && path.parentPath.isBlockStatement()) {
|
547 |
|
548 |
|
549 | path.parentPath.pushContext(path.context);
|
550 | path.parentPath.visit();
|
551 | path.parentPath.popContext();
|
552 | return;
|
553 | }
|
554 |
|
555 | if (node.argument) {
|
556 | return;
|
557 | }
|
558 |
|
559 | let noNext = true;
|
560 | let parentPath = path.parentPath;
|
561 |
|
562 | while (parentPath && !parentPath.isFunction() && noNext) {
|
563 |
|
564 | if (hasLoopParent(parentPath)) {
|
565 | noNext = false;
|
566 | break;
|
567 | }
|
568 |
|
569 | const nextPath = parentPath.getSibling(parentPath.key + 1);
|
570 |
|
571 | if (nextPath.node) {
|
572 | if (nextPath.isReturnStatement()) {
|
573 | nextPath.pushContext(path.context);
|
574 | nextPath.visit();
|
575 | nextPath.popContext();
|
576 |
|
577 | if (parentPath.getSibling(parentPath.key + 1).node) {
|
578 | noNext = false;
|
579 | break;
|
580 | }
|
581 | } else {
|
582 | noNext = false;
|
583 | break;
|
584 | }
|
585 | }
|
586 |
|
587 | parentPath = parentPath.parentPath;
|
588 | }
|
589 |
|
590 | if (noNext) {
|
591 | removeOrVoid(path);
|
592 | }
|
593 | },
|
594 |
|
595 | ConditionalExpression(path) {
|
596 | const node = path.node;
|
597 | const evaluateTest = evaluateTruthy(path.get("test"));
|
598 |
|
599 | if (evaluateTest === true) {
|
600 | path.replaceWith(node.consequent);
|
601 | } else if (evaluateTest === false) {
|
602 | path.replaceWith(node.alternate);
|
603 | }
|
604 | },
|
605 |
|
606 | SwitchStatement: {
|
607 | exit(path) {
|
608 | const evaluated = evaluate(path.get("discriminant"), {
|
609 | tdz: this.tdz
|
610 | });
|
611 | if (!evaluated.confident) return;
|
612 | const discriminant = evaluated.value;
|
613 | const cases = path.get("cases");
|
614 | let matchingCaseIndex = -1;
|
615 | let defaultCaseIndex = -1;
|
616 |
|
617 | for (let i = 0; i < cases.length; i++) {
|
618 | const test = cases[i].get("test");
|
619 |
|
620 | if (test.node === null) {
|
621 | defaultCaseIndex = i;
|
622 | continue;
|
623 | }
|
624 |
|
625 | const testResult = evaluate(test, {
|
626 | tdz: this.tdz
|
627 | });
|
628 |
|
629 |
|
630 | if (!testResult.confident) return;
|
631 |
|
632 | if (testResult.value === discriminant) {
|
633 | matchingCaseIndex = i;
|
634 | break;
|
635 | }
|
636 | }
|
637 |
|
638 | let result;
|
639 |
|
640 | if (matchingCaseIndex === -1) {
|
641 | if (defaultCaseIndex === -1) {
|
642 | path.skip();
|
643 | path.replaceWithMultiple(extractVars(path));
|
644 | return;
|
645 | } else {
|
646 | result = getStatementsUntilBreak(defaultCaseIndex);
|
647 | }
|
648 | } else {
|
649 | result = getStatementsUntilBreak(matchingCaseIndex);
|
650 | }
|
651 |
|
652 | if (result.bail) return;
|
653 |
|
654 |
|
655 |
|
656 | replaceSwitch([...extractVars(path), ...result.statements]);
|
657 |
|
658 | function getStatementsUntilBreak(start) {
|
659 | const result = {
|
660 | bail: false,
|
661 | statements: []
|
662 | };
|
663 |
|
664 | for (let i = start; i < cases.length; i++) {
|
665 | const consequent = cases[i].get("consequent");
|
666 |
|
667 | for (let j = 0; j < consequent.length; j++) {
|
668 | const _isBreaking = isBreaking(consequent[j], path);
|
669 |
|
670 | if (_isBreaking.bail) {
|
671 | result.bail = true;
|
672 | return result;
|
673 | }
|
674 |
|
675 | if (_isBreaking.break) {
|
676 |
|
677 |
|
678 | return result;
|
679 | } else {
|
680 | result.statements.push(consequent[j].node);
|
681 | }
|
682 | }
|
683 | }
|
684 |
|
685 | return result;
|
686 | }
|
687 |
|
688 | function replaceSwitch(statements) {
|
689 | let isBlockRequired = false;
|
690 |
|
691 | for (let i = 0; i < statements.length; i++) {
|
692 | if (t.isVariableDeclaration(statements[i], {
|
693 | kind: "let"
|
694 | })) {
|
695 | isBlockRequired = true;
|
696 | break;
|
697 | }
|
698 |
|
699 | if (t.isVariableDeclaration(statements[i], {
|
700 | kind: "const"
|
701 | })) {
|
702 | isBlockRequired = true;
|
703 | break;
|
704 | }
|
705 | }
|
706 |
|
707 | if (isBlockRequired) {
|
708 | path.replaceWith(t.BlockStatement(statements));
|
709 | } else {
|
710 | path.replaceWithMultiple(statements);
|
711 | }
|
712 | }
|
713 | }
|
714 |
|
715 | },
|
716 |
|
717 | WhileStatement(path) {
|
718 | const test = path.get("test");
|
719 | const result = evaluate(test, {
|
720 | tdz: this.tdz
|
721 | });
|
722 |
|
723 | if (result.confident && test.isPure() && !result.value) {
|
724 | path.remove();
|
725 | }
|
726 | },
|
727 |
|
728 | ForStatement(path) {
|
729 | const test = path.get("test");
|
730 | if (!test.isPure()) return;
|
731 | const result = evaluate(test, {
|
732 | tdz: this.tdz
|
733 | });
|
734 |
|
735 | if (result.confident) {
|
736 | if (result.value) {
|
737 | test.remove();
|
738 | } else {
|
739 | const init = path.get("init");
|
740 |
|
741 | if (init.node && !init.isPure()) {
|
742 | path.replaceWith(init);
|
743 | } else {
|
744 | path.remove();
|
745 | }
|
746 | }
|
747 | }
|
748 | },
|
749 |
|
750 | DoWhileStatement(path) {
|
751 | const test = path.get("test");
|
752 | const result = evaluate(test, {
|
753 | tdz: this.tdz
|
754 | });
|
755 |
|
756 | if (result.confident && test.isPure() && !result.value) {
|
757 | const body = path.get("body");
|
758 |
|
759 | if (body.isBlockStatement()) {
|
760 | const stmts = body.get("body");
|
761 | var _iteratorNormalCompletion4 = true;
|
762 | var _didIteratorError4 = false;
|
763 | var _iteratorError4 = undefined;
|
764 |
|
765 | try {
|
766 | for (var _iterator4 = stmts[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
|
767 | const stmt = _step4.value;
|
768 |
|
769 | const _isBreaking = isBreaking(stmt, path);
|
770 |
|
771 | if (_isBreaking.bail || _isBreaking.break) return;
|
772 |
|
773 | const _isContinuing = isContinuing(stmt, path);
|
774 |
|
775 | if (_isContinuing.bail || isContinuing.continue) return;
|
776 | }
|
777 | } catch (err) {
|
778 | _didIteratorError4 = true;
|
779 | _iteratorError4 = err;
|
780 | } finally {
|
781 | try {
|
782 | if (!_iteratorNormalCompletion4 && _iterator4.return != null) {
|
783 | _iterator4.return();
|
784 | }
|
785 | } finally {
|
786 | if (_didIteratorError4) {
|
787 | throw _iteratorError4;
|
788 | }
|
789 | }
|
790 | }
|
791 |
|
792 | path.replaceWith(body.node);
|
793 | } else if (body.isBreakStatement()) {
|
794 | const _isBreaking = isBreaking(body, path);
|
795 |
|
796 | if (_isBreaking.bail) return;
|
797 | if (_isBreaking.break) path.remove();
|
798 | } else if (body.isContinueStatement()) {
|
799 | return;
|
800 | } else {
|
801 | path.replaceWith(body.node);
|
802 | }
|
803 | }
|
804 | },
|
805 |
|
806 |
|
807 |
|
808 | AssignmentExpression(path) {
|
809 | if (!path.get("left").isIdentifier() || !path.parentPath.isExpressionStatement()) {
|
810 | return;
|
811 | }
|
812 |
|
813 | const prev = path.parentPath.getSibling(path.parentPath.key - 1);
|
814 |
|
815 | if (!(prev && prev.isVariableDeclaration())) {
|
816 | return;
|
817 | }
|
818 |
|
819 | const declars = prev.node.declarations;
|
820 |
|
821 | if (declars.length !== 1 || declars[0].init || declars[0].id.name !== path.get("left").node.name) {
|
822 | return;
|
823 | }
|
824 |
|
825 | declars[0].init = path.node.right;
|
826 | removeOrVoid(path);
|
827 | },
|
828 |
|
829 |
|
830 |
|
831 | FunctionExpression(path) {
|
832 | if (!this.keepFnName) {
|
833 | removeUnreferencedId(path);
|
834 | }
|
835 | },
|
836 |
|
837 |
|
838 | ClassExpression(path) {
|
839 | if (!this.keepClassName) {
|
840 | removeUnreferencedId(path);
|
841 | }
|
842 | },
|
843 |
|
844 |
|
845 | ForInStatement(path) {
|
846 | const left = path.get("left");
|
847 |
|
848 | if (!left.isIdentifier()) {
|
849 | return;
|
850 | }
|
851 |
|
852 | const binding = path.scope.getBinding(left.node.name);
|
853 |
|
854 | if (!binding) {
|
855 | return;
|
856 | }
|
857 |
|
858 | if (binding.scope.getFunctionParent() !== path.scope.getFunctionParent()) {
|
859 | return;
|
860 | }
|
861 |
|
862 | if (!binding.path.isVariableDeclarator()) {
|
863 | return;
|
864 | }
|
865 |
|
866 | if (binding.path.parentPath.parentPath.isForInStatement({
|
867 | left: binding.path.parent
|
868 | })) {
|
869 | return;
|
870 | }
|
871 |
|
872 |
|
873 | if (binding.path.parent.declarations.length > 1) {
|
874 | return;
|
875 | }
|
876 |
|
877 |
|
878 | if (binding.path.node.init) {
|
879 | return;
|
880 | }
|
881 |
|
882 | removeOrVoid(binding.path);
|
883 | path.node.left = t.variableDeclaration("var", [t.variableDeclarator(left.node)]);
|
884 | binding.path = path.get("left").get("declarations")[0];
|
885 | }
|
886 |
|
887 | };
|
888 | return {
|
889 | name: "minify-dead-code-elimination",
|
890 | visitor: {
|
891 | Function: {
|
892 | exit(path) {
|
893 | |
894 |
|
895 |
|
896 |
|
897 | const body = path.get("body");
|
898 |
|
899 | if (body.isBlockStatement()) {
|
900 | removeUseStrict(body);
|
901 | }
|
902 | }
|
903 |
|
904 | },
|
905 | IfStatement: {
|
906 | exit(path, {
|
907 | opts: {
|
908 | tdz = false
|
909 | } = {}
|
910 | }) {
|
911 | const consequent = path.get("consequent");
|
912 | const alternate = path.get("alternate");
|
913 | const test = path.get("test");
|
914 | const evalResult = evaluate(test, {
|
915 | tdz
|
916 | });
|
917 | const isPure = test.isPure();
|
918 | const replacements = [];
|
919 |
|
920 | if (evalResult.confident && !isPure && test.isSequenceExpression()) {
|
921 | replacements.push(t.expressionStatement(extractSequenceImpure(test)));
|
922 | }
|
923 |
|
924 |
|
925 |
|
926 |
|
927 |
|
928 |
|
929 |
|
930 | if (evalResult.confident && evalResult.value) {
|
931 | path.replaceWithMultiple([...replacements, ...toStatements(consequent), ...extractVars(alternate)]);
|
932 | return;
|
933 | }
|
934 |
|
935 |
|
936 |
|
937 |
|
938 |
|
939 |
|
940 |
|
941 | if (evalResult.confident && !evalResult.value) {
|
942 | if (alternate.node) {
|
943 | path.replaceWithMultiple([...replacements, ...toStatements(alternate), ...extractVars(consequent)]);
|
944 | return;
|
945 | } else {
|
946 | path.replaceWithMultiple([...replacements, ...extractVars(consequent)]);
|
947 | }
|
948 | }
|
949 |
|
950 |
|
951 |
|
952 |
|
953 |
|
954 | if (alternate.isBlockStatement() && !alternate.node.body.length) {
|
955 | alternate.remove();
|
956 |
|
957 | path.node.alternate = null;
|
958 | }
|
959 |
|
960 |
|
961 |
|
962 |
|
963 |
|
964 |
|
965 | if (consequent.isBlockStatement() && !consequent.node.body.length && alternate.isBlockStatement() && alternate.node.body.length) {
|
966 | consequent.replaceWith(alternate.node);
|
967 | alternate.remove();
|
968 |
|
969 | path.node.alternate = null;
|
970 | test.replaceWith(t.unaryExpression("!", test.node, true));
|
971 | }
|
972 | }
|
973 |
|
974 | },
|
975 |
|
976 | EmptyStatement(path) {
|
977 | if (path.parentPath.isBlockStatement() || path.parentPath.isProgram()) {
|
978 | path.remove();
|
979 | }
|
980 | },
|
981 |
|
982 | Program: {
|
983 | exit(path, {
|
984 | opts: {
|
985 |
|
986 | optimizeRawSize = false,
|
987 | keepFnName = false,
|
988 | keepClassName = false,
|
989 | keepFnArgs = false,
|
990 | tdz = false
|
991 | } = {}
|
992 | } = {}) {
|
993 | (traverse.clearCache || traverse.cache.clear)();
|
994 | path.scope.crawl();
|
995 | markEvalScopes(path);
|
996 |
|
997 | path.traverse(main, {
|
998 | functionToBindings: new Map(),
|
999 | optimizeRawSize,
|
1000 | keepFnName,
|
1001 | keepClassName,
|
1002 | keepFnArgs,
|
1003 | tdz
|
1004 | });
|
1005 | }
|
1006 |
|
1007 | }
|
1008 | }
|
1009 | };
|
1010 |
|
1011 | function toStatements(path) {
|
1012 | const node = path.node;
|
1013 |
|
1014 | if (path.isBlockStatement()) {
|
1015 | let hasBlockScoped = false;
|
1016 |
|
1017 | for (let i = 0; i < node.body.length; i++) {
|
1018 | const bodyNode = node.body[i];
|
1019 |
|
1020 | if (t.isBlockScoped(bodyNode)) {
|
1021 | hasBlockScoped = true;
|
1022 | }
|
1023 | }
|
1024 |
|
1025 | if (!hasBlockScoped) {
|
1026 | return node.body;
|
1027 | }
|
1028 | }
|
1029 |
|
1030 | return [node];
|
1031 | }
|
1032 |
|
1033 |
|
1034 |
|
1035 |
|
1036 |
|
1037 |
|
1038 |
|
1039 | function extractVars(path) {
|
1040 | const declarators = [];
|
1041 |
|
1042 | if (path.isVariableDeclaration({
|
1043 | kind: "var"
|
1044 | })) {
|
1045 | var _iteratorNormalCompletion5 = true;
|
1046 | var _didIteratorError5 = false;
|
1047 | var _iteratorError5 = undefined;
|
1048 |
|
1049 | try {
|
1050 | for (var _iterator5 = path.node.declarations[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
|
1051 | const decl = _step5.value;
|
1052 | const bindingIds = Object.keys(t.getBindingIdentifiers(decl.id));
|
1053 | declarators.push(...bindingIds.map(name => t.variableDeclarator(t.identifier(name))));
|
1054 | }
|
1055 | } catch (err) {
|
1056 | _didIteratorError5 = true;
|
1057 | _iteratorError5 = err;
|
1058 | } finally {
|
1059 | try {
|
1060 | if (!_iteratorNormalCompletion5 && _iterator5.return != null) {
|
1061 | _iterator5.return();
|
1062 | }
|
1063 | } finally {
|
1064 | if (_didIteratorError5) {
|
1065 | throw _iteratorError5;
|
1066 | }
|
1067 | }
|
1068 | }
|
1069 | } else {
|
1070 | path.traverse({
|
1071 | VariableDeclaration(varPath) {
|
1072 | if (!varPath.isVariableDeclaration({
|
1073 | kind: "var"
|
1074 | })) return;
|
1075 | if (!isSameFunctionScope(varPath, path)) return;
|
1076 | var _iteratorNormalCompletion6 = true;
|
1077 | var _didIteratorError6 = false;
|
1078 | var _iteratorError6 = undefined;
|
1079 |
|
1080 | try {
|
1081 | for (var _iterator6 = varPath.node.declarations[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
|
1082 | const decl = _step6.value;
|
1083 | const bindingIds = Object.keys(t.getBindingIdentifiers(decl.id));
|
1084 | declarators.push(...bindingIds.map(name => t.variableDeclarator(t.identifier(name))));
|
1085 | }
|
1086 | } catch (err) {
|
1087 | _didIteratorError6 = true;
|
1088 | _iteratorError6 = err;
|
1089 | } finally {
|
1090 | try {
|
1091 | if (!_iteratorNormalCompletion6 && _iterator6.return != null) {
|
1092 | _iterator6.return();
|
1093 | }
|
1094 | } finally {
|
1095 | if (_didIteratorError6) {
|
1096 | throw _iteratorError6;
|
1097 | }
|
1098 | }
|
1099 | }
|
1100 | }
|
1101 |
|
1102 | });
|
1103 | }
|
1104 |
|
1105 | if (declarators.length <= 0) return [];
|
1106 | return [t.variableDeclaration("var", declarators)];
|
1107 | }
|
1108 |
|
1109 | function replace(path, options) {
|
1110 | const replacement = options.replacement,
|
1111 | replacementPath = options.replacementPath,
|
1112 | scope = options.scope,
|
1113 | binding = options.binding;
|
1114 |
|
1115 | if (scope.getBinding(path.node.name) !== binding) {
|
1116 | return;
|
1117 | }
|
1118 |
|
1119 |
|
1120 |
|
1121 |
|
1122 | if (scope !== path.scope) {
|
1123 | if (t.isClass(replacement) || t.isFunction(replacement)) {
|
1124 | return;
|
1125 | }
|
1126 |
|
1127 | let bail = false;
|
1128 | traverse(replacement, {
|
1129 | Function(path) {
|
1130 | if (bail) {
|
1131 | return;
|
1132 | }
|
1133 |
|
1134 | bail = true;
|
1135 | path.stop();
|
1136 | }
|
1137 |
|
1138 | }, scope);
|
1139 |
|
1140 | if (bail) {
|
1141 | return;
|
1142 | }
|
1143 | }
|
1144 |
|
1145 |
|
1146 | if (path.find(({
|
1147 | node
|
1148 | }) => node === replacement)) {
|
1149 | return;
|
1150 | }
|
1151 |
|
1152 |
|
1153 |
|
1154 |
|
1155 | if (replacementPath.isFunctionDeclaration()) {
|
1156 | const fnName = replacementPath.get("id").node.name;
|
1157 |
|
1158 | for (let name in replacementPath.scope.bindings) {
|
1159 | if (name === fnName) {
|
1160 | return;
|
1161 | }
|
1162 | }
|
1163 | }
|
1164 |
|
1165 |
|
1166 | if (!t.isExpression(replacement)) {
|
1167 | t.toExpression(replacement);
|
1168 | }
|
1169 |
|
1170 |
|
1171 |
|
1172 |
|
1173 |
|
1174 |
|
1175 | path.replaceWith(replacement);
|
1176 | return true;
|
1177 | }
|
1178 |
|
1179 | function updateReferences(fnToDeletePath) {
|
1180 | if (!fnToDeletePath.isFunction()) {
|
1181 | return;
|
1182 | }
|
1183 |
|
1184 | fnToDeletePath.traverse({
|
1185 | ReferencedIdentifier(path) {
|
1186 | const node = path.node,
|
1187 | scope = path.scope;
|
1188 | const binding = scope.getBinding(node.name);
|
1189 |
|
1190 | if (!binding || !binding.path.isFunction() || binding.scope === scope || !binding.constant) {
|
1191 | return;
|
1192 | }
|
1193 |
|
1194 | const index = binding.referencePaths.indexOf(path);
|
1195 |
|
1196 | if (index === -1) {
|
1197 | return;
|
1198 | }
|
1199 |
|
1200 | binding.references--;
|
1201 | binding.referencePaths.splice(index, 1);
|
1202 |
|
1203 | if (binding.references === 0) {
|
1204 | binding.referenced = false;
|
1205 | }
|
1206 |
|
1207 | if (binding.references <= 1 && binding.scope.path.node) {
|
1208 | binding.scope.path.node[shouldRevisit] = true;
|
1209 | }
|
1210 | }
|
1211 |
|
1212 | });
|
1213 | }
|
1214 |
|
1215 | function removeUnreferencedId(path) {
|
1216 | const id = path.get("id").node;
|
1217 |
|
1218 | if (!id) {
|
1219 | return;
|
1220 | }
|
1221 |
|
1222 | const node = path.node,
|
1223 | scope = path.scope;
|
1224 | const binding = scope.getBinding(id.name);
|
1225 |
|
1226 | if (binding && (binding.path.node !== node || !binding.referenced)) {
|
1227 | node.id = null;
|
1228 | }
|
1229 | }
|
1230 |
|
1231 |
|
1232 |
|
1233 | function isAncestor(path1, path2) {
|
1234 | return !!path2.findParent(parent => parent === path1);
|
1235 | }
|
1236 |
|
1237 | function isSameFunctionScope(path1, path2) {
|
1238 | return path1.scope.getFunctionParent() === path2.scope.getFunctionParent();
|
1239 | }
|
1240 |
|
1241 | function isBreaking(stmt, path) {
|
1242 | return isControlTransfer(stmt, path, "break");
|
1243 | }
|
1244 |
|
1245 | function isContinuing(stmt, path) {
|
1246 | return isControlTransfer(stmt, path, "continue");
|
1247 | }
|
1248 |
|
1249 |
|
1250 | function isControlTransfer(stmt, path, control = "break") {
|
1251 | const _break$continue = {
|
1252 | break: "BreakStatement",
|
1253 | continue: "ContinueStatement"
|
1254 | },
|
1255 | type = _break$continue[control];
|
1256 |
|
1257 | if (!type) {
|
1258 | throw new Error("Can only handle break and continue statements");
|
1259 | }
|
1260 |
|
1261 | const checker = `is${type}`;
|
1262 |
|
1263 | if (stmt[checker]()) {
|
1264 | return _isControlTransfer(stmt, path);
|
1265 | }
|
1266 |
|
1267 | let isTransferred = false;
|
1268 | let result = {
|
1269 | [control]: false,
|
1270 | bail: false
|
1271 | };
|
1272 | stmt.traverse({
|
1273 | [type](cPath) {
|
1274 |
|
1275 | if (isTransferred) return;
|
1276 | result = _isControlTransfer(cPath, path);
|
1277 |
|
1278 | if (result.bail || result[control]) {
|
1279 | isTransferred = true;
|
1280 | }
|
1281 | }
|
1282 |
|
1283 | });
|
1284 | return result;
|
1285 |
|
1286 | function _isControlTransfer(cPath, path) {
|
1287 | const label = cPath.get("label");
|
1288 |
|
1289 | if (label.node !== null) {
|
1290 |
|
1291 |
|
1292 | if (!isSameFunctionScope(path, cPath)) {
|
1293 |
|
1294 | return {
|
1295 | break: false,
|
1296 | bail: false
|
1297 | };
|
1298 | }
|
1299 |
|
1300 |
|
1301 |
|
1302 |
|
1303 | let labelPath;
|
1304 |
|
1305 | if (path.scope.getLabel) {
|
1306 | labelPath = getLabel(label.node.name, path);
|
1307 | } else {
|
1308 | labelPath = path.scope.getBinding(label.node.name).path;
|
1309 | }
|
1310 |
|
1311 | const _isAncestor = isAncestor(labelPath, path);
|
1312 |
|
1313 | return {
|
1314 | bail: _isAncestor,
|
1315 | [control]: _isAncestor
|
1316 | };
|
1317 | }
|
1318 |
|
1319 |
|
1320 | let isCTransfer = true;
|
1321 |
|
1322 |
|
1323 | let possibleRunTimeControlTransfer = false;
|
1324 |
|
1325 | let parent = cPath.parentPath;
|
1326 |
|
1327 | while (parent !== stmt.parentPath) {
|
1328 |
|
1329 | if (parent.isLoop() || parent.isSwitchCase()) {
|
1330 |
|
1331 |
|
1332 | possibleRunTimeControlTransfer = false;
|
1333 |
|
1334 | isCTransfer = false;
|
1335 | break;
|
1336 | }
|
1337 |
|
1338 |
|
1339 |
|
1340 |
|
1341 |
|
1342 |
|
1343 |
|
1344 |
|
1345 |
|
1346 |
|
1347 |
|
1348 |
|
1349 |
|
1350 |
|
1351 |
|
1352 |
|
1353 | if (parent.isIfStatement()) {
|
1354 | possibleRunTimeControlTransfer = true;
|
1355 | }
|
1356 |
|
1357 | parent = parent.parentPath;
|
1358 | }
|
1359 |
|
1360 | return {
|
1361 | [control]: possibleRunTimeControlTransfer || isCTransfer,
|
1362 | bail: possibleRunTimeControlTransfer
|
1363 | };
|
1364 | }
|
1365 | }
|
1366 |
|
1367 |
|
1368 | function canExistAfterCompletion(path) {
|
1369 | return path.isFunctionDeclaration() || path.isVariableDeclaration({
|
1370 | kind: "var"
|
1371 | });
|
1372 | }
|
1373 |
|
1374 | function getLabel(name, _path) {
|
1375 | let label,
|
1376 | path = _path;
|
1377 |
|
1378 | do {
|
1379 | label = path.scope.getLabel(name);
|
1380 |
|
1381 | if (label) {
|
1382 | return label;
|
1383 | }
|
1384 | } while (path = path.parentPath);
|
1385 |
|
1386 | return null;
|
1387 | }
|
1388 |
|
1389 | function hasLoopParent(path) {
|
1390 | let parent = path;
|
1391 |
|
1392 | do {
|
1393 | if (parent.isLoop()) {
|
1394 | return true;
|
1395 | }
|
1396 | } while (parent = parent.parentPath);
|
1397 |
|
1398 | return false;
|
1399 | }
|
1400 |
|
1401 | function extractSequenceImpure(seq) {
|
1402 | const expressions = seq.get("expressions");
|
1403 | const result = [];
|
1404 |
|
1405 | for (let i = 0; i < expressions.length; i++) {
|
1406 | if (!expressions[i].isPure()) {
|
1407 | result.push(expressions[i].node);
|
1408 | }
|
1409 | }
|
1410 |
|
1411 | return t.sequenceExpression(result);
|
1412 | }
|
1413 | }; |
\ | No newline at end of file |