@@ -34,6 +34,19 @@ function safeGetName(path) {
3434 return null
3535}
3636
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+
3750function safeDeleteNode ( name , path ) {
3851 let binding
3952 if ( path . isFunctionDeclaration ( ) ) {
@@ -1557,6 +1570,124 @@ const deGlobalConcealing = {
15571570 } ,
15581571}
15591572
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+
15601691module . exports = function ( code ) {
15611692 let ast
15621693 try {
@@ -1587,6 +1718,9 @@ module.exports = function (code) {
15871718 traverse ( ast , pruneIfBranch )
15881719 // GlobalConcealing
15891720 traverse ( ast , deGlobalConcealing )
1721+ // ControlFlowFlattening
1722+ traverse ( ast , deControlFlowFlatteningStateless )
1723+ traverse ( ast , calculateConstantExp )
15901724 code = generator ( ast , {
15911725 comments : false ,
15921726 jsescOption : { minimal : true } ,
0 commit comments