file author meixuguang
  import * as ts from 'byots' ; export function transform ( context ) { const { startLexicalEnvironment } = context ; let destructTempId = 0 ; const resolver = context . getEmitResolver ( ) ; context . enableSubstitution ( ts . SyntaxKind . PropertyAccessExpression ) ; context . enableSubstitution ( ts . SyntaxKind . ElementAccessExpression ) ; let currentNamespaceContainerName ; let currentLexicalScope ; let currentNameScope ; let currentScopeFirstDeclarationsOfName ; return transformSourceFileOrBundle ; function transformSourceFileOrBundle ( node ) { return transformSourceFile ( node ) ; } param node function transformSourceFile ( node ) { if ( node . isDeclarationFile ) { return node ; } const visited = saveStateAndInvoke ( node , visitSourceFile ) ; return visited ; } param node function saveStateAndInvoke < T > ( node , f ) { const savedCurrentScope = currentLexicalScope ; const savedCurrentNameScope = currentNameScope ; const savedCurrentScopeFirstDeclarationsOfName = currentScopeFirstDeclarationsOfName ; onBeforeVisitNode ( node ) ; const visited = f ( node ) ; if ( currentLexicalScope !== savedCurrentScope ) { currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName ; } currentLexicalScope = savedCurrentScope ; currentNameScope = savedCurrentNameScope ; return visited ; } param node function onBeforeVisitNode ( node ) { switch ( node . kind ) { case ts . SyntaxKind . SourceFile : case ts . SyntaxKind . CaseBlock : case ts . SyntaxKind . ModuleBlock : case ts . SyntaxKind . Block : currentLexicalScope = < ts . SourceFile | ts . CaseBlock | ts . ModuleBlock | ts . Block > node ; currentNameScope = undefined ; currentScopeFirstDeclarationsOfName = undefined ; break ; } } function visitSourceFile ( node ) { return ts . updateSourceFileNode ( node , ts . visitLexicalEnvironment ( node . statements , sourceElementVisitor , context , 0 , false ) ) ; } param node function sourceElementVisitor ( node ) { return saveStateAndInvoke ( node , visitTypeScript ) ; } param node function visitTypeScript ( node ) { const originalNode = node ; node = ts . visitEachChild ( node , visitTypeScript , context ) ; if ( node !== originalNode ) { node . parent = originalNode . parent ; ts . setOriginalNode ( node , originalNode ) ; ts . setTextRange ( node , originalNode ) ; } switch ( node . kind ) { case ts . SyntaxKind . EnumDeclaration : return visitEnumDeclaration ( < ts . EnumDeclaration > node ) ; case ts . SyntaxKind . FunctionDeclaration : return visitFunctionDeclaration ( < ts . FunctionDeclaration > node ) ; case ts . SyntaxKind . FunctionExpression : return visitFunctionExpression ( < ts . FunctionExpression > node ) ; case ts . SyntaxKind . MethodDeclaration : return visitMethodDeclaration ( < ts . MethodDeclaration > node ) ; case ts . SyntaxKind . CallExpression : return visitCallExpression ( < ts . CallExpression > node ) ; case ts . SyntaxKind . VariableDeclarationList : return visitVariableDeclarationList ( < ts . VariableDeclarationList > node ) ; case ts . SyntaxKind . ObjectLiteralExpression : return visitObjectLiteralExpression ( < ts . ObjectLiteralExpression > node ) ; case ts . SyntaxKind . ArrayLiteralExpression : return visitArrayLiteralExpression ( < ts . ArrayLiteralExpression > node ) ; case ts . SyntaxKind . ExpressionStatement : return visitExpressionStatement ( < ts . ExpressionStatement > node ) ; case ts . SyntaxKind . ClassDeclaration : return visitClassDeclaration ( < ts . ClassDeclaration > node ) ; default : return node ; } } function visitExpressionStatement ( node ) { if ( ! ts . isBinaryExpression ( node . expression ) || node . expression . operatorToken . kind !== ts . SyntaxKind . EqualsToken || ! ts . isArrayLiteralExpression ( node . expression . left ) ) { return node ; } const initializerName = 'destruct_temp_' + ( ++ destructTempId ) ; const temp = ts . createVariableDeclaration ( initializerName , undefined , visitTypeScript ( < ts . Node > node . expression . right ) ) ; temp . name . parent = temp ; ts . setTextRange ( temp , node . expression . right ) ; const arrayLiteral = node . expression . left ; const declarations = arrayLiteral . elements . reduce ( ( prev , e , index ) => { if ( ! ts . isIdentifier ( e ) && ! ts . isBinaryExpression ( e ) ) { return prev ; } const initializer = ts . createIdentifier ( initializerName ) ; const access = ts . createElementAccess ( initializer , index ) ; ts . setTextRange ( access , initializer ) ; access . expression . parent = access ; let variable ; if ( ts . isIdentifier ( e ) ) { variable = ts . createVariableDeclaration ( e . getText ( ) , undefined , access ) ; } else { const variableName = ( < ts . BinaryExpression > e ) . left . getText ( ) ; const call = ts . createCall ( ts . createIdentifier ( 'isset' ) , [ ] , [ access ] ) ; call . expression . parent = call ; call . arguments . forEach ( a => { a . parent = call ; } ) ; const conditional = ts . createConditional ( call , access , ( < ts . BinaryExpression > e ) . right ) ; conditional . forEachChild ( e => { e . parent = conditional ; } ) ; variable = ts . createVariableDeclaration ( variableName , undefined , conditional ) ; } variable . initializer . parent = variable ; variable . name . parent = variable ; ts . setTextRange ( variable , e ) ; prev . push ( variable ) ; return prev ; } , [ temp ] as ts . VariableDeclaration [ ] ) ; const list = ts . createVariableDeclarationList ( declarations , ts . NodeFlags . Let ) ; const statement = ts . createVariableStatement ( undefined , list ) ; statement . declarationList . parent = statement ; ts . setTextRange ( list , node . expression ) ; return statement ; } function transformSpread ( nodeList , type ) { const createNode = children => type === 'object' ? ts . createObjectLiteral ( children , true ) : ts . createArrayLiteral ( children , true ) ; const isSpread = node => type === 'object' ? ts . isSpreadAssignment ( node ) : ts . isSpreadElement ( node ) ; const parameters = [ createNode ( [ ] ) ] ; let children = [ ] ; for ( const prop of nodeList ) { if ( isSpread ( prop ) ) { if ( children . length > 0 ) { parameters . push ( createNode ( children ) ) ; children = [ ] ; } parameters . push ( prop . expression ) ; } else { children . push ( prop ) ; } } if ( children . length > 0 ) { parameters . push ( createNode ( children ) ) ; } return ts . createCall ( ts . createIdentifier ( 'array_merge' ) , undefined , parameters ) ; } function visitArrayLiteralExpression ( node ) { if ( ! node . elements . some ( node => ts . isSpreadElement ( node ) ) ) { return node ; } return transformSpread ( node . elements , 'array' ) ; } function visitObjectLiteralExpression ( node ) { if ( ! node . properties . some ( node => ts . isSpreadAssignment ( node ) ) ) { return node ; } return transformSpread ( node . properties , 'object' ) ; } function visitVariableDeclarationList ( node ) { const pendingMap = new Map ( ) ; node . declarations . forEach ( ( declaration , index ) => { const nameNode = declaration . name ; if ( ! ts . isObjectBindingPattern ( nameNode ) && ! ts . isArrayBindingPattern ( nameNode ) ) { return ; } const initializer = declaration . initializer ; const pendingList = [ ] as ts . VariableDeclaration [ ] ; const isArray = ts . isArrayBindingPattern ( nameNode ) ; nameNode . elements . forEach ( ( child , index ) => { if ( ! ts . isBindingElement ( child ) ) { return ; } const accessName = child . propertyName || child . name ; const access = ts . createElementAccess ( initializer , isArray ? index : ts . createStringLiteral ( accessName . getText ( ) ) ) ; const variable = ts . createVariableDeclaration ( child . name , undefined , child . initializer ? ts . createConditional ( ts . createCall ( ts . createIdentifier ( 'isset' ) , [ ] , [ access ] ) , access , child . initializer ) : access ) ; pendingList . push ( variable ) ; } ) ; pendingMap . set ( index , pendingList ) ; } ) ; if ( ! pendingMap . size ) { return node ; } let newDeclarations = [ ] ; let start = 0 ; pendingMap . forEach ( ( declarations , index ) => { newDeclarations = [ ... newDeclarations , ... node . declarations . slice ( start , index - start ) , ... declarations ] ; start = index + 1 ; } ) ; return ts . updateVariableDeclarationList ( node , newDeclarations ) ; } function visitCallExpression ( node ) { const index = node . arguments . findIndex ( node => ts . isSpreadElement ( node ) ) ; if ( index < 0 ) { return node ; } const spread = node . arguments [ index ] as ts . SpreadElement ; const spreadName = ts . createIdentifier ( spread . expression . getText ( ) ) ; let argument = spreadName ; if ( index > 0 ) { const argus = node . arguments . map ( node => node ) ; const leadingArgu = ts . createArrayLiteral ( argus . slice ( 0 , index ) ) ; const suffix = argus . slice ( index + 1 ) ; const newArgus = [ leadingArgu , spreadName , ] ; if ( suffix . length ) { newArgus . push ( ts . createArrayLiteral ( suffix ) ) ; } argument = ts . createCall ( ts . createIdentifier ( 'array_merge' ) , [ ] , newArgus ) ; } const name = node . expression . getText ( ) as string ; const returnNode = ts . updateCall ( node , ts . createIdentifier ( 'call_user_func_array' ) , [ ] , [ ts . createStringLiteral ( name ) , argument ] ) ; if ( index > 0 ) { spreadName . parent = argument ; } else { spreadName . parent = returnNode ; } return returnNode ; } function visitFunctionDeclaration ( node ) { if ( ! node . parameters . some ( node => node . dotDotDotToken ) ) { return node ; } const body = transformFunctionBody ( node ) ; return ts . updateFunctionDeclaration ( node , undefined , undefined , node . asteriskToken , node . name , undefined , node . parameters , undefined , body ) ; } function visitFunctionExpression ( node ) { if ( ! node . parameters . some ( node => node . dotDotDotToken ) ) { return node ; } const body = transformFunctionBody ( node ) ; return ts . updateFunctionExpression ( node , undefined , node . asteriskToken , node . name , undefined , node . parameters , undefined , body ) ; } function visitMethodDeclaration ( node ) { if ( ! node . parameters . some ( node => node . dotDotDotToken ) ) { return node ; } const body = transformFunctionBody ( node ) ; return ts . updateMethod ( node , undefined , undefined , node . asteriskToken , node . name , node . questionToken , undefined , node . parameters , undefined , body ) ; } function transformFunctionBody ( node ) { const parameterIndex = node . parameters . findIndex ( node => ! ! node . dotDotDotToken ) ; if ( parameterIndex < 0 ) { return node . body ; } const parameter = node . parameters [ parameterIndex ] ; const declarationName = parameter . name . kind === ts . SyntaxKind . Identifier ? ts . getMutableClone ( parameter . name ) : ts . createTempVariable ( undefined ) ; const parameters = [ ... node . parameters ] ; node . parameters = ts . createNodeArray ( [ ] ) ; let variableDeclarationList ; if ( parameterIndex === 0 ) { variableDeclarationList = ts . createVariableDeclarationList ( [ ts . createVariableDeclaration ( declarationName , undefined , ts . createCall ( ts . createIdentifier ( 'func_get_args' ) , [ ] , [ ] ) ) ] ) ; } else { const list = parameters . slice ( 0 , parameters . length - 1 ) . map ( ( parameter , index ) => ts . createVariableDeclaration ( parameter . name , undefined , ts . createCall ( ts . createIdentifier ( 'func_get_arg' ) , [ ] , [ ts . createNumericLiteral ( `${ index }` ) ] ) ) ) ; list . push ( ts . createVariableDeclaration ( declarationName , undefined , ts . createCall ( ts . createIdentifier ( 'array_slice' ) , undefined , [ ts . createCall ( ts . createIdentifier ( 'func_get_args' ) , undefined , [ ] ) , ts . createNumericLiteral ( `${ parameterIndex }` ) ] ) ) ) ; variableDeclarationList = ts . createVariableDeclarationList ( list ) ; } const statement = ts . createVariableStatement ( undefined , variableDeclarationList ) ; return ts . createBlock ( [ statement , ... node . body . statements ] , true ) ; } param node function visitEnumDeclaration ( node ) { const containerName = getNamespaceContainerName ( node ) ; const exportName = ts . getLocalName ( node , false , true ) ; const enumStatement = ts . createVariableStatement ( undefined , [ ts . createVariableDeclaration ( exportName , undefined , transformEnumBody ( node , containerName ) ) ] ) ; return enumStatement ; } /**
     * Gets the expression used to refer to a namespace or enum within the body
     * of its declaration.
     */ function getNamespaceContainerName ( node ) { return ts . getGeneratedNameForNode ( node ) ; } param node function transformEnumBody ( node , localName ) { const savedCurrentNamespaceLocalName = currentNamespaceContainerName ; currentNamespaceContainerName = localName ; startLexicalEnvironment ( ) ; const members = ts . map ( node . members , transformEnumMember ) ; currentNamespaceContainerName = savedCurrentNamespaceLocalName ; return ts . createObjectLiteral ( members ) ; } param member function transformEnumMember ( member ) { const valueExpression = transformEnumMemberDeclarationValue ( member ) ; return ts . createPropertyAssignment ( member . name , valueExpression ) ; } param member function transformEnumMemberDeclarationValue ( member ) { const value = resolver . getConstantValue ( member ) ; if ( value !== undefined ) { return ts . createLiteral ( value ) ; } else { if ( member . initializer ) { return ts . visitNode ( member . initializer , visitor , ts . isExpression ) ; } else { return ts . createVoidZero ( ) ; } } } function visitClassDeclaration ( node ) { const initAfter = [ ] ; node . members . forEach ( item => { if ( item . kind === ts . SyntaxKind . PropertyDeclaration && item . modifiers && item . modifiers . find ( item => item . kind === ts . SyntaxKind . StaticKeyword ) && ( item as ts . PropertyDeclaration ) . initializer && ! ts . isStringLiteralLike ( ( item as ts . PropertyDeclaration ) . initializer ) && ! ts . isNumericLiteral ( ( item as ts . PropertyDeclaration ) . initializer ) ) { initAfter . push ( { key : item . name , initializer : ( item as ts . PropertyDeclaration ) . initializer } ) ; ( item as ts . PropertyDeclaration ) . initializer = undefined ; } } ) ; const res = [ ] ; res . push ( node ) ; initAfter . forEach ( item => { const init = ts . createExpressionStatement ( ts . createBinary ( ts . createPropertyAccess ( node . name , item . key . getText ( ) ) , ts . createToken ( ts . SyntaxKind . EqualsToken ) , item . initializer ) ) ; res . push ( init ) ; } ) ; return res ; } param node function visitor ( node ) { return saveStateAndInvoke ( node , visitTypeScript ) ; } }