import * as ts from 'typescript' ; import * as util from '../util' ; import { Mutator } from './Mutator' ; type MethodLikeProperty = ts . ConstructorDeclaration | ts . MethodDeclaration | ts . SetAccessorDeclaration | ts . GetAccessorDeclaration ; type PropertySubstitution = ts . PropertyDeclaration | ts . GetAccessorDeclaration | ts . SetAccessorDeclaration ; export class ClassDeclarationMutator extends Mutator { protected kind = ts . SyntaxKind . ClassDeclaration ; protected initializers : ts . Statement [ ] = [ ] ; protected mutate ( node ) { const members = [ ] ; const decorators = this . options . noAnnotate ? node . decorators : this . reflectClass ( node ) ; for ( let member of node . members ) { switch ( member . kind ) { case ts . SyntaxKind . Constructor : case ts . SyntaxKind . MethodDeclaration : case ts . SyntaxKind . GetAccessor : case ts . SyntaxKind . SetAccessor : members . push ( this . mutateMethodDeclaration ( member as MethodLikeProperty ) ) ; break ; case ts . SyntaxKind . PropertyDeclaration : members . push ( ... util . asArray ( this . mutatePropertyDeclaration ( member as ts . PropertyDeclaration ) ) ) ; break ; case ts . SyntaxKind . IndexSignature : default : members . push ( member ) ; } } this . addInitializers ( node , members ) ; this . declareTypeParameters ( node , members ) ; this . assertImplementing ( node , members ) ; this . setMerged ( node ) ; return ts . updateClassDeclaration ( node , decorators , node . modifiers , node . name , node . typeParameters , node . heritageClauses , members ) ; } private setMerged ( node ) { const nodeSymbol = this . scanner . getNodeSymbol ( node . name ) ; this . context . setMerged ( nodeSymbol ) ; } private reflectClass ( node ) { const classReflection = this . factory . classReflection ( node ) ; const decorators = util . arrayFromNodeArray ( node . decorators ) ; const decorator = ts . createDecorator ( this . factory . annotate ( classReflection ) ) ; decorators . unshift ( decorator ) ; return decorators ; } private addInitializers ( node , members ) { if ( this . initializers . length === 0 ) { return ; } const constructor = this . getConstructor ( members ) ; let statements = util . arrayFromNodeArray ( constructor . body . statements ) ; for ( let initializer of this . initializers ) { statements = util . insertAfterSuper ( statements , initializer ) ; } this . updateConstructor ( members , constructor , statements ) ; } private assertImplementing ( node , members ) { const implementsClause = util . getImplementsClause ( node ) ; if ( ! implementsClause ) { return members ; } let constructor = this . getConstructor ( members ) ; let statements = util . arrayFromNodeArray ( constructor . body . statements ) ; for ( let impl of implementsClause . types || [ ] ) { const assertion = ts . createStatement ( this . factory . typeAssertion ( this . factory . expressionWithTypeArgumentsReflection ( impl ) , ts . createThis ( ) ) ) ; statements . push ( assertion ) ; } this . updateConstructor ( members , constructor , statements ) ; return members ; } private declareTypeParameters ( node , members ) { const extendsClause = util . getExtendsClause ( node ) ; const hasTypeParameters = util . hasTypeParameters ( node ) ; const extendsClauseHasTypeArguments = util . extendsClauseHasTypeArguments ( extendsClause ) ; if ( ! hasTypeParameters && ! extendsClauseHasTypeArguments ) { return members ; } let constructor = this . getConstructor ( members ) ; let statements = util . arrayFromNodeArray ( constructor . body . statements ) ; let typeParametersStatement ; let thisStatement ; let bindStatement ; const insert = [ ] ; if ( hasTypeParameters ) { typeParametersStatement = this . factory . typeParametersLiteralDeclaration ( util . arrayFromNodeArray ( node . typeParameters ) ) ; thisStatement = this . factory . classTypeParameterSymbolConstructorDeclaration ( node . name ) ; insert . push ( typeParametersStatement ) ; } if ( extendsClauseHasTypeArguments ) { bindStatement = this . factory . typeParameterBindingDeclaration ( util . arrayFromNodeArray ( extendsClause . types [ 0 ] . typeArguments ) ) ; } insert . push ( ... [ thisStatement , bindStatement ] . filter ( statement => ! ! statement ) ) ; util . insertAfterSuper ( statements , insert ) ; this . updateConstructor ( members , constructor , statements ) ; if ( hasTypeParameters ) { members . unshift ( this . factory . classTypeParameterSymbolPropertyDeclaration ( node . name ) ) ; } return members ; } private mutatePropertyDeclaration ( node ) { if ( ! this . options . assertAny && this . context . isAny ( node . type ) ) { return node ; } if ( ts . isComputedPropertyName ( node . name ) ) { return node ; } if ( util . hasModifier ( node , ts . SyntaxKind . AbstractKeyword ) ) { return node ; } const isStatic = util . isStatic ( node ) ; const className = ( this . node as ts . ClassDeclaration ) . name . text ; let name = node . name . text ; if ( ! ts . isComputedPropertyName ( node . name ) ) { name = this . context . getPropertyName ( this . node as ts . ClassDeclaration , `_${ node . name . text }` ) ; } let initializer = undefined ; if ( node . initializer ) { initializer = this . factory . typeReflectionAndAssertion ( node . type , node . initializer ) ; } if ( ! isStatic && initializer ) { this . initializers . push ( ts . createStatement ( ts . createBinary ( ts . createPropertyAccess ( isStatic ? ts . createIdentifier ( className ) : ts . createThis ( ) , name ) , ts . SyntaxKind . FirstAssignment , initializer ) ) ) ; } const property = this . map ( ts . updateProperty ( node , node . decorators , node . modifiers , ts . createIdentifier ( name ) , node . questionToken , node . type , isStatic ? initializer : undefined ) , node ) ; let setAccessor ; if ( ! util . hasModifier ( node , ts . SyntaxKind . ReadonlyKeyword ) ) { setAccessor = this . factory . mutateFunctionBody ( ts . createSetAccessor ( undefined , node . modifiers , node . name , [ ts . createParameter ( undefined , undefined , undefined , node . name . text , undefined , node . type ) ] , ts . createBlock ( [ ts . createStatement ( ts . createBinary ( ts . createPropertyAccess ( isStatic ? ts . createIdentifier ( className ) : ts . createThis ( ) , name ) , ts . SyntaxKind . FirstAssignment , node . name as ts . Expression ) ) ] , true ) ) ) as ts . SetAccessorDeclaration ; } const getAccessor = ts . createGetAccessor ( undefined , node . modifiers , node . name , undefined , node . type , ts . createBlock ( [ ts . createReturn ( ts . createPropertyAccess ( isStatic ? ts . createIdentifier ( className ) : ts . createThis ( ) , name ) ) ] , true ) ) ; ( node . name as any ) = ts . createIdentifier ( name ) ; return [ property , getAccessor , setAccessor ] . filter ( val => ! ! val ) ; } private mutateMethodDeclaration ( node ) { return this . factory . mutateFunctionBody ( node ) as MethodLikeProperty ; } private getConstructor ( members , create = true ) { const index = members . findIndex ( member => member . kind === ts . SyntaxKind . Constructor ) ; const exists = index !== - 1 ; if ( exists ) { return members [ index ] as ts . ConstructorDeclaration ; } if ( ! create ) { return null ; } const extendsClause = util . getExtendsClause ( this . node as ts . ClassDeclaration ) ; const isExtending = ! ! extendsClause ; const constructor = ts . createConstructor ( undefined , undefined , isExtending ? [ ts . createParameter ( undefined , undefined , ts . createToken ( ts . SyntaxKind . DotDotDotToken ) , 'args' ) ] : undefined , ts . createBlock ( isExtending ? [ ts . createStatement ( ts . createCall ( ts . createSuper ( ) , undefined , [ ts . createSpread ( ts . createIdentifier ( 'args' ) ) ] ) ) ] : [ ] , true ) ) ; return constructor ; } private updateConstructor ( members , constructor , statements ) { const index = members . findIndex ( member => member . kind === ts . SyntaxKind . Constructor ) ; const exists = index !== - 1 ; constructor = this . map ( ts . updateConstructor ( constructor , constructor . decorators , constructor . modifiers , constructor . parameters , this . map ( ts . updateBlock ( constructor . body , statements ) , constructor . body ) ) , constructor ) ; if ( exists ) { members [ index ] = constructor ; } else { members . unshift ( constructor ) ; } return members ; } }