@@ -34,6 +34,19 @@ function safeGetName(path) {
34
34
return null
35
35
}
36
36
37
+ function safeGetLiteral ( path ) {
38
+ if ( path . isUnaryExpression ( ) ) {
39
+ if ( path . node . operator === '-' && path . get ( 'argument' ) . isNumericLiteral ( ) ) {
40
+ return - 1 * path . get ( 'argument' ) . node . value
41
+ }
42
+ return null
43
+ }
44
+ if ( path . isLiteral ( ) ) {
45
+ return path . node . value
46
+ }
47
+ return null
48
+ }
49
+
37
50
function safeDeleteNode ( name , path ) {
38
51
let binding
39
52
if ( path . isFunctionDeclaration ( ) ) {
@@ -1557,6 +1570,124 @@ const deGlobalConcealing = {
1557
1570
} ,
1558
1571
}
1559
1572
1573
+ function checkControlVar ( path ) {
1574
+ const parent = path . parentPath
1575
+ if ( path . key !== 'right' || ! parent . isAssignmentExpression ( ) ) {
1576
+ return false
1577
+ }
1578
+ const var_path = parent . get ( 'left' )
1579
+ const var_name = var_path . node ?. name
1580
+ if ( ! var_name ) {
1581
+ return false
1582
+ }
1583
+ let root_path = parent . parentPath
1584
+ if ( root_path . isExpressionStatement ) {
1585
+ root_path = root_path . parentPath
1586
+ }
1587
+ const binding = parent . scope . getBinding ( var_name )
1588
+ for ( const ref of binding . referencePaths ) {
1589
+ if ( ref === var_path ) {
1590
+ continue
1591
+ }
1592
+ let cur = ref
1593
+ let valid = false
1594
+ while ( cur && cur !== root_path ) {
1595
+ if ( cur . isSwitchCase ( ) || cur === path ) {
1596
+ valid = true
1597
+ break
1598
+ }
1599
+ cur = cur . parentPath
1600
+ }
1601
+ if ( ! valid ) {
1602
+ return false
1603
+ }
1604
+ if ( ref . key === 'object' ) {
1605
+ const prop = ref . parentPath . get ( 'property' )
1606
+ if ( ! prop . isLiteral ( ) && ! prop . isIdentifier ( ) ) {
1607
+ return false
1608
+ }
1609
+ continue
1610
+ }
1611
+ if ( ref . key === 'right' ) {
1612
+ const left = ref . parentPath . get ( 'left' )
1613
+ if ( ! left . isMemberExpression ( ) ) {
1614
+ return false
1615
+ }
1616
+ const obj = safeGetName ( left . get ( 'object' ) )
1617
+ if ( obj !== var_name ) {
1618
+ return false
1619
+ }
1620
+ continue
1621
+ }
1622
+ }
1623
+ return true
1624
+ }
1625
+
1626
+ /**
1627
+ * Process the constant properties in the controlVar
1628
+ *
1629
+ * Template:
1630
+ * ```javascript
1631
+ * controlVar = {
1632
+ * // strings
1633
+ * key_string: 'StringLiteral',
1634
+ * // numbers
1635
+ * key_number: 'NumericLiteral',
1636
+ * }
1637
+ * ```
1638
+ *
1639
+ * Some kinds of deadCode may in inserted to the fake chunks:
1640
+ *
1641
+ * ```javascript
1642
+ * controlVar = false
1643
+ * controlVar = undefined
1644
+ * controlVar[randomControlKey] = undefined
1645
+ * delete controlVar[randomControlKey]
1646
+ * ```
1647
+ */
1648
+ const deControlFlowFlatteningStateless = {
1649
+ ObjectExpression ( path ) {
1650
+ if ( ! checkControlVar ( path ) ) {
1651
+ return
1652
+ }
1653
+ const parent = path . parentPath
1654
+ const var_name = parent . get ( 'left' ) . node ?. name
1655
+ console . log ( `[ControlFlowFlattening] parse stateless in obj: ${ var_name } ` )
1656
+ const props = { }
1657
+ const prop_num = path . node . properties . length
1658
+ for ( let i = 0 ; i < prop_num ; ++ i ) {
1659
+ const prop = path . get ( `properties.${ i } ` )
1660
+ const key = safeGetName ( prop . get ( 'key' ) )
1661
+ const value = safeGetLiteral ( prop . get ( 'value' ) )
1662
+ if ( ! key || ! value ) {
1663
+ continue
1664
+ }
1665
+ props [ key ] = value
1666
+ }
1667
+ const binding = parent . scope . getBinding ( var_name )
1668
+ for ( const ref of binding . referencePaths ) {
1669
+ if ( ref . key !== 'object' ) {
1670
+ continue
1671
+ }
1672
+ const prop = safeGetName ( ref . parentPath . get ( 'property' ) )
1673
+ if ( ! prop ) {
1674
+ continue
1675
+ }
1676
+ if ( ! Object . prototype . hasOwnProperty . call ( props , prop ) ) {
1677
+ continue
1678
+ }
1679
+ const upper = ref . parentPath
1680
+ if ( upper . key === 'left' && upper . parentPath . isAssignmentExpression ( ) ) {
1681
+ // this is in the fake chunk
1682
+ ref . parentPath . parentPath . remove ( )
1683
+ continue
1684
+ }
1685
+ safeReplace ( ref . parentPath , props [ prop ] )
1686
+ }
1687
+ binding . scope . crawl ( )
1688
+ } ,
1689
+ }
1690
+
1560
1691
module . exports = function ( code ) {
1561
1692
let ast
1562
1693
try {
@@ -1587,6 +1718,9 @@ module.exports = function (code) {
1587
1718
traverse ( ast , pruneIfBranch )
1588
1719
// GlobalConcealing
1589
1720
traverse ( ast , deGlobalConcealing )
1721
+ // ControlFlowFlattening
1722
+ traverse ( ast , deControlFlowFlatteningStateless )
1723
+ traverse ( ast , calculateConstantExp )
1590
1724
code = generator ( ast , {
1591
1725
comments : false ,
1592
1726
jsescOption : { minimal : true } ,
0 commit comments