namespace ts . codefix { const fixId = "convertFunctionToEs6Class" ; const errorCodes = [ Diagnostics . This_constructor_function_may_be_converted_to_a_class_declaration . code ] ; registerCodeFix ( { errorCodes , getCodeActions ( context ) { const changes = textChanges . ChangeTracker . with ( context , t => doChange ( t , context . sourceFile , context . span . start , context . program . getTypeChecker ( ) , context . preferences , context . program . getCompilerOptions ( ) ) ) ; return [ createCodeFixAction ( fixId , changes , Diagnostics . Convert_function_to_an_ES2015_class , fixId , Diagnostics . Convert_all_constructor_functions_to_classes ) ] ; } , fixIds : [ fixId ] , getAllCodeActions : context => codeFixAll ( context , errorCodes , ( changes , err ) => doChange ( changes , err . file , err . start , context . program . getTypeChecker ( ) , context . preferences , context . program . getCompilerOptions ( ) ) ) , } ) ; function doChange ( changes , sourceFile , position , checker , preferences , compilerOptions ) { const ctorSymbol = checker . getSymbolAtLocation ( getTokenAtPosition ( sourceFile , position ) ) ! ; if ( ! ctorSymbol || ! ctorSymbol . valueDeclaration || ! ( ctorSymbol . flags & ( SymbolFlags . Function | SymbolFlags . Variable ) ) ) { return undefined ; } const ctorDeclaration = ctorSymbol . valueDeclaration ; if ( isFunctionDeclaration ( ctorDeclaration ) ) { changes . replaceNode ( sourceFile , ctorDeclaration , createClassFromFunctionDeclaration ( ctorDeclaration ) ) ; } else if ( isVariableDeclaration ( ctorDeclaration ) ) { const classDeclaration = createClassFromVariableDeclaration ( ctorDeclaration ) ; if ( ! classDeclaration ) { return undefined ; } const ancestor = ctorDeclaration . parent . parent ; if ( isVariableDeclarationList ( ctorDeclaration . parent ) && ctorDeclaration . parent . declarations . length > 1 ) { changes . delete ( sourceFile , ctorDeclaration ) ; changes . insertNodeAfter ( sourceFile , ancestor , classDeclaration ) ; } else { changes . replaceNode ( sourceFile , ancestor , classDeclaration ) ; } } function createClassElementsFromSymbol ( symbol ) { const memberElements = [ ] ; if ( symbol . members ) { symbol . members . forEach ( ( member , key ) => { if ( key === "constructor" && member . valueDeclaration ) { changes . delete ( sourceFile , member . valueDeclaration . parent ) ; return ; } const memberElement = createClassElement ( member , undefined ) ; if ( memberElement ) { memberElements . push ( ... memberElement ) ; } } ) ; } if ( symbol . exports ) { symbol . exports . forEach ( member => { if ( member . name === "prototype" && member . declarations ) { const firstDeclaration = member . declarations [ 0 ] ; if ( member . declarations . length === 1 && isPropertyAccessExpression ( firstDeclaration ) && isBinaryExpression ( firstDeclaration . parent ) && firstDeclaration . parent . operatorToken . kind === SyntaxKind . EqualsToken && isObjectLiteralExpression ( firstDeclaration . parent . right ) ) { const prototypes = firstDeclaration . parent . right ; const memberElement = createClassElement ( prototypes . symbol , undefined ) ; if ( memberElement ) { memberElements . push ( ... memberElement ) ; } } } else { const memberElement = createClassElement ( member , [ factory . createToken ( SyntaxKind . StaticKeyword ) ] ) ; if ( memberElement ) { memberElements . push ( ... memberElement ) ; } } } ) ; } return memberElements ; function shouldConvertDeclaration ( _target , source ) { if ( isAccessExpression ( _target ) ) { if ( isPropertyAccessExpression ( _target ) && isConstructorAssignment ( _target ) ) return true ; return isFunctionLike ( source ) ; } else { return every ( _target . properties , property => { if ( isMethodDeclaration ( property ) || isGetOrSetAccessorDeclaration ( property ) ) return true ; if ( isPropertyAssignment ( property ) && isFunctionExpression ( property . initializer ) && ! ! property . name ) return true ; if ( isConstructorAssignment ( property ) ) return true ; return false ; } ) ; } } function createClassElement ( symbol , modifiers ) { const members = [ ] ; if ( ! ( symbol . flags & SymbolFlags . Method ) && ! ( symbol . flags & SymbolFlags . ObjectLiteral ) ) { return members ; } const memberDeclaration = symbol . valueDeclaration as AccessExpression | ObjectLiteralExpression ; const assignmentBinaryExpression = memberDeclaration . parent as BinaryExpression ; const assignmentExpr = assignmentBinaryExpression . right ; if ( ! shouldConvertDeclaration ( memberDeclaration , assignmentExpr ) ) { return members ; } const nodeToDelete = assignmentBinaryExpression . parent && assignmentBinaryExpression . parent . kind === SyntaxKind . ExpressionStatement ? assignmentBinaryExpression . parent : assignmentBinaryExpression ; changes . delete ( sourceFile , nodeToDelete ) ; if ( ! assignmentExpr ) { members . push ( factory . createPropertyDeclaration ( [ ] , modifiers , symbol . name , undefined , undefined , undefined ) ) ; return members ; } if ( isAccessExpression ( memberDeclaration ) && ( isFunctionExpression ( assignmentExpr ) || isArrowFunction ( assignmentExpr ) ) ) { const quotePreference = getQuotePreference ( sourceFile , preferences ) ; const name = tryGetPropertyName ( memberDeclaration , compilerOptions , quotePreference ) ; if ( name ) { return createFunctionLikeExpressionMember ( members , assignmentExpr , name ) ; } return members ; } else if ( isObjectLiteralExpression ( assignmentExpr ) ) { return flatMap ( assignmentExpr . properties , property => { if ( isMethodDeclaration ( property ) || isGetOrSetAccessorDeclaration ( property ) ) { return members . concat ( property ) ; } if ( isPropertyAssignment ( property ) && isFunctionExpression ( property . initializer ) ) { return createFunctionLikeExpressionMember ( members , property . initializer , property . name ) ; } if ( isConstructorAssignment ( property ) ) return members ; return [ ] ; } ) ; } else { if ( isSourceFileJS ( sourceFile ) ) return members ; if ( ! isPropertyAccessExpression ( memberDeclaration ) ) return members ; const prop = factory . createPropertyDeclaration ( undefined , modifiers , memberDeclaration . name , undefined , undefined , assignmentExpr ) ; copyLeadingComments ( assignmentBinaryExpression . parent , prop , sourceFile ) ; members . push ( prop ) ; return members ; } function createFunctionLikeExpressionMember ( members , expression , name ) { if ( isFunctionExpression ( expression ) ) return createFunctionExpressionMember ( members , expression , name ) ; else return createArrowFunctionExpressionMember ( members , expression , name ) ; } function createFunctionExpressionMember ( members , functionExpression , name ) { const fullModifiers = concatenate ( modifiers , getModifierKindFromSource ( functionExpression , SyntaxKind . AsyncKeyword ) ) ; const method = factory . createMethodDeclaration ( undefined , fullModifiers , undefined , name , undefined , undefined , functionExpression . parameters , undefined , functionExpression . body ) ; copyLeadingComments ( assignmentBinaryExpression , method , sourceFile ) ; return members . concat ( method ) ; } function createArrowFunctionExpressionMember ( members , arrowFunction , name ) { const arrowFunctionBody = arrowFunction . body ; let bodyBlock ; if ( arrowFunctionBody . kind === SyntaxKind . Block ) { bodyBlock = arrowFunctionBody as Block ; } else { bodyBlock = factory . createBlock ( [ factory . createReturnStatement ( arrowFunctionBody ) ] ) ; } const fullModifiers = concatenate ( modifiers , getModifierKindFromSource ( arrowFunction , SyntaxKind . AsyncKeyword ) ) ; const method = factory . createMethodDeclaration ( undefined , fullModifiers , undefined , name , undefined , undefined , arrowFunction . parameters , undefined , bodyBlock ) ; copyLeadingComments ( assignmentBinaryExpression , method , sourceFile ) ; return members . concat ( method ) ; } } } function createClassFromVariableDeclaration ( node ) { const initializer = node . initializer ; if ( ! initializer || ! isFunctionExpression ( initializer ) || ! isIdentifier ( node . name ) ) { return undefined ; } const memberElements = createClassElementsFromSymbol ( node . symbol ) ; if ( initializer . body ) { memberElements . unshift ( factory . createConstructorDeclaration ( undefined , undefined , initializer . parameters , initializer . body ) ) ; } const modifiers = getModifierKindFromSource ( node . parent . parent , SyntaxKind . ExportKeyword ) ; const cls = factory . createClassDeclaration ( undefined , modifiers , node . name , undefined , undefined , memberElements ) ; return cls ; } function createClassFromFunctionDeclaration ( node ) { const memberElements = createClassElementsFromSymbol ( ctorSymbol ) ; if ( node . body ) { memberElements . unshift ( factory . createConstructorDeclaration ( undefined , undefined , node . parameters , node . body ) ) ; } const modifiers = getModifierKindFromSource ( node , SyntaxKind . ExportKeyword ) ; const cls = factory . createClassDeclaration ( undefined , modifiers , node . name , undefined , undefined , memberElements ) ; return cls ; } } function getModifierKindFromSource ( source , kind ) { return filter ( source . modifiers , modifier => modifier . kind === kind ) ; } function isConstructorAssignment ( x ) { if ( ! x . name ) return false ; if ( isIdentifier ( x . name ) && x . name . text === "constructor" ) return true ; return false ; } function tryGetPropertyName ( node , compilerOptions , quotePreference ) { if ( isPropertyAccessExpression ( node ) ) { return node . name ; } const propName = node . argumentExpression ; if ( isNumericLiteral ( propName ) ) { return propName ; } if ( isStringLiteralLike ( propName ) ) { return isIdentifierText ( propName . text , getEmitScriptTarget ( compilerOptions ) ) ? factory . createIdentifier ( propName . text ) : isNoSubstitutionTemplateLiteral ( propName ) ? factory . createStringLiteral ( propName . text , quotePreference === QuotePreference . Single ) : propName ; } return undefined ; } }