@@ -255,9 +255,9 @@ export default function (babel) {
255255 return body ;
256256 }
257257
258- function ensureBlockBody ( path ) {
259- if ( ! t . isBlockStatement ( path . node . body ) ) {
260- path . get ( "body" ) . replaceWith ( t . blockStatement ( [ path . node . body ] ) ) ;
258+ function ensureBlockBody ( path , bodyKey = "body" ) {
259+ if ( ! path . get ( bodyKey ) . isBlockStatement ( ) ) {
260+ path . get ( bodyKey ) . replaceWith ( t . blockStatement ( [ path . node [ bodyKey ] ] ) ) ;
261261 }
262262 }
263263
@@ -663,7 +663,7 @@ export default function (babel) {
663663 }
664664 }
665665
666- function generateForInIterator ( path , type : "array" | "object" ) {
666+ function generateForInIterator ( path , type : "array" | "object" ) {
667667 const idx = path . node . idx || path . scope . generateUidIdentifier ( "i" ) ;
668668 const len = path . scope . generateUidIdentifier ( "len" ) ;
669669
@@ -758,6 +758,117 @@ export default function (babel) {
758758 return t . forStatement ( init , test , update , path . node . body ) ;
759759 }
760760
761+ function addAlternateToIfChain ( rootIf , alternate ) {
762+ let tail = rootIf
763+ while ( tail . alternate ) tail = tail . alternate ;
764+ tail . alternate = alternate ;
765+ return rootIf ;
766+ }
767+
768+ function isUndefined ( path ) {
769+ return path . isIdentifier ( ) && path . node . name === "undefined" ;
770+ }
771+
772+ function isSignedNumber ( path ) {
773+ return path . isUnaryExpression ( ) &&
774+ path . get ( "argument" ) . isNumericLiteral ( ) &&
775+ ( path . node . operator === "+" || path . node . operator === "-" ) ;
776+ }
777+
778+ function looksLikeClassName ( path ) {
779+ // disallow Foo.Bar for now.
780+ if ( ! path . isIdentifier ( ) ) return false ;
781+ const { name } = path . node ;
782+ if ( name [ 0 ] . toUpperCase ( ) === name [ 0 ] ) {
783+ // A -> true
784+ if ( name . length === 1 ) return true ;
785+ // ABC -> false, it's a constant
786+ if ( name . toUpperCase ( ) === name ) return false ;
787+ // Abc -> true
788+ return true ;
789+ }
790+ return false ;
791+ }
792+
793+ function isPrimitiveClass ( { node : { name } } ) {
794+ switch ( name ) {
795+ case "Number ":
796+ case "String ":
797+ case "Boolean ":
798+ return true ;
799+ default :
800+ return false ;
801+ }
802+ }
803+
804+ function transformMatchCaseTest ( path , argRef ) {
805+ if ( path . isLogicalExpression ( ) ) {
806+ transformMatchCaseTest ( path . get ( "left" ) , argRef ) ;
807+ transformMatchCaseTest ( path . get ( "right" ) , argRef ) ;
808+ } else if ( path . isUnaryExpression ( ) && path . node . operator === "!" ) {
809+ transformMatchCaseTest ( path . get ( "argument" ) , argRef ) ;
810+ } else if ( path . isRegExpLiteral ( ) ) {
811+ const testCall = t . callExpression (
812+ t . memberExpression ( path . node , t . identifier ( "test" ) ) ,
813+ [ argRef ]
814+ ) ;
815+ path . replaceWith ( testCall ) ;
816+ } else if ( looksLikeClassName ( path ) ) {
817+ const classInstanceCheck = isPrimitiveClass ( path )
818+ ? t . binaryExpression ( "===" ,
819+ t . unaryExpression ( "typeof" , argRef ) ,
820+ t . stringLiteral ( path . node . name . toLowerCase ( ) )
821+ )
822+ : t . binaryExpression ( "instanceof" , argRef , path . node )
823+ path . replaceWith ( classInstanceCheck ) ;
824+ } else if ( path . isLiteral ( ) || isUndefined ( path ) || isSignedNumber ( path ) ) {
825+ const isEq = t . binaryExpression ( "===" , argRef , path . node ) ;
826+ path . replaceWith ( isEq ) ;
827+ }
828+ }
829+
830+ function transformMatchCases ( argRef , cases ) {
831+ return cases . reduce ( ( rootIf , path ) => {
832+
833+ // fill in placeholders
834+ path . get ( "test" ) . traverse ( {
835+ PlaceholderExpression ( placeholderPath ) {
836+ placeholderPath . replaceWith ( argRef ) ;
837+ }
838+ } ) ;
839+
840+ // add in ===, etc
841+ transformMatchCaseTest ( path . get ( "test" ) , argRef ) ;
842+
843+ // add binding (and always use block bodies)
844+ ensureBlockBody ( path , "consequent" ) ;
845+ if ( path . node . binding ) {
846+ const bindingDecl = t . variableDeclaration ( "const" , [
847+ t . variableDeclarator ( path . node . binding , argRef )
848+ ] ) ;
849+ path . get ( "consequent" ) . unshiftContainer ( "body" , bindingDecl ) ;
850+ }
851+
852+ // handle `else`
853+ if ( path . node . test . type === "MatchElse" ) {
854+ if ( rootIf ) {
855+ return addAlternateToIfChain ( rootIf , path . node . consequent ) ;
856+ } else {
857+ // single match case with "else", weird. Just generate an anonymous block for now.
858+ return path . node . consequent ;
859+ }
860+ }
861+
862+ // generate `if` and append to if-else chain
863+ const ifStmt = t . ifStatement ( path . node . test , path . node . consequent ) ;
864+ if ( ! rootIf ) {
865+ return ifStmt ;
866+ } else {
867+ return addAlternateToIfChain ( rootIf , ifStmt ) ;
868+ }
869+ } , null ) ;
870+ }
871+
761872 // TYPE DEFINITIONS
762873 definePluginType ( "ForInArrayStatement" , {
763874 visitor : [ "idx" , "elem" , "array" , "body" ] ,
@@ -960,6 +1071,55 @@ export default function (babel) {
9601071 aliases : [ "MemberExpression" , "Expression" , "LVal" ] ,
9611072 } ) ;
9621073
1074+ definePluginType ( "MatchExpression" , {
1075+ builder : [ "discriminant" , "cases" ] ,
1076+ visitor : [ "discriminant" , "cases" ] ,
1077+ aliases : [ "Expression" , "Conditional" ] ,
1078+ fields : {
1079+ discriminant : {
1080+ validate : assertNodeType ( "Expression" )
1081+ } ,
1082+ cases : {
1083+ validate : chain ( assertValueType ( "array" ) , assertEach ( assertNodeType ( "MatchCase" ) ) )
1084+ }
1085+ }
1086+ } ) ;
1087+
1088+ definePluginType ( "MatchStatement" , {
1089+ builder : [ "discriminant" , "cases" ] ,
1090+ visitor : [ "discriminant" , "cases" ] ,
1091+ aliases : [ "Statement" , "Conditional" ] ,
1092+ fields : {
1093+ discriminant : {
1094+ validate : assertNodeType ( "Expression" )
1095+ } ,
1096+ cases : {
1097+ validate : chain ( assertValueType ( "array" ) , assertEach ( assertNodeType ( "MatchCase" ) ) )
1098+ }
1099+ }
1100+ } ) ;
1101+
1102+ definePluginType ( "MatchCase" , {
1103+ builder : [ "test" , "consequent" , "functional" ] ,
1104+ visitor : [ "test" , "consequent" ] ,
1105+ fields : {
1106+ test : {
1107+ validate : assertNodeType ( "Expression" , "MatchElse" )
1108+ } ,
1109+ consequent : {
1110+ validate : assertNodeType ( "BlockStatement" , "ExpressionStatement" )
1111+ }
1112+ }
1113+ } ) ;
1114+
1115+ definePluginType ( "MatchElse" , {
1116+ // only allowed in MatchCase, so don't alias to Expression
1117+ } ) ;
1118+
1119+ definePluginType ( "PlaceholderExpression" , {
1120+ aliases : [ "Expression" ]
1121+ } ) ;
1122+
9631123 // traverse as top-level item so as to run before other babel plugins
9641124 // (and avoid traversing any of their output)
9651125 function Program ( path , state ) {
@@ -1258,6 +1418,35 @@ export default function (babel) {
12581418 }
12591419 } ,
12601420
1421+ MatchExpression ( path ) {
1422+ const { discriminant } = path . node ;
1423+
1424+ const argRef = path . scope . generateUidIdentifier ( "it" ) ;
1425+ const matchBody = transformMatchCases ( argRef , path . get ( "cases" ) ) ;
1426+
1427+ const iife = t . callExpression (
1428+ t . arrowFunctionExpression ( [ argRef ] , t . blockStatement ( [ matchBody ] ) ) ,
1429+ [ discriminant ]
1430+ ) ;
1431+ path . replaceWith ( iife ) ;
1432+ } ,
1433+
1434+ MatchStatement ( path ) {
1435+ const { discriminant } = path . node ;
1436+
1437+ let argRef ;
1438+ if ( t . isIdentifier ( discriminant ) ) {
1439+ argRef = discriminant ;
1440+ } else {
1441+ argRef = path . scope . generateUidIdentifier ( "it" ) ;
1442+ path . insertBefore ( t . variableDeclaration ( "const" , [
1443+ t . variableDeclarator ( argRef , discriminant )
1444+ ] ) ) ;
1445+ }
1446+
1447+ const matchBody = transformMatchCases ( argRef , path . get ( "cases" ) ) ;
1448+ path . replaceWith ( matchBody ) ;
1449+ } ,
12611450
12621451 } ) ;
12631452
0 commit comments