@@ -1006,11 +1006,14 @@ namespace ts.refactor.extractSymbol {
10061006 const localNameText = getUniqueName ( isClassLike ( scope ) ? "newProperty" : "newLocal" , file ) ;
10071007 const isJS = isInJSFile ( scope ) ;
10081008
1009- const variableType = isJS || ! checker . isContextSensitive ( node )
1009+ let variableType = isJS || ! checker . isContextSensitive ( node )
10101010 ? undefined
10111011 : checker . typeToTypeNode ( checker . getContextualType ( node ) ! , scope , NodeBuilderFlags . NoTruncation ) ; // TODO: GH#18217
10121012
1013- const initializer = transformConstantInitializer ( node , substitutions ) ;
1013+ let initializer = transformConstantInitializer ( node , substitutions ) ;
1014+
1015+ ( { variableType, initializer } = transformFunctionInitializerAndType ( variableType , initializer ) ) ;
1016+
10141017 suppressLeadingAndTrailingTrivia ( initializer ) ;
10151018
10161019 const changeTracker = textChanges . ChangeTracker . fromContext ( context ) ;
@@ -1102,6 +1105,73 @@ namespace ts.refactor.extractSymbol {
11021105 const renameFilename = node . getSourceFile ( ) . fileName ;
11031106 const renameLocation = getRenameLocation ( edits , renameFilename , localNameText , /*isDeclaredBeforeUse*/ true ) ;
11041107 return { renameFilename, renameLocation, edits } ;
1108+
1109+ function transformFunctionInitializerAndType ( variableType : TypeNode | undefined , initializer : Expression ) : { variableType : TypeNode | undefined , initializer : Expression } {
1110+ // If no contextual type exists there is nothing to transfer to the function signature
1111+ if ( variableType === undefined ) return { variableType, initializer } ;
1112+ // Only do this for function expressions and arrow functions that are not generic
1113+ if ( ! isFunctionExpression ( initializer ) && ! isArrowFunction ( initializer ) || ! ! initializer . typeParameters ) return { variableType, initializer } ;
1114+ const functionType = checker . getTypeAtLocation ( node ) ;
1115+ const functionSignature = singleOrUndefined ( checker . getSignaturesOfType ( functionType , SignatureKind . Call ) ) ;
1116+
1117+ // If no function signature, maybe there was an error, do nothing
1118+ if ( ! functionSignature ) return { variableType, initializer } ;
1119+ // If the function signature has generic type parameters we don't attempt to move the parameters
1120+ if ( ! ! functionSignature . getTypeParameters ( ) ) return { variableType, initializer } ;
1121+
1122+ // We add parameter types if needed
1123+ const parameters : ParameterDeclaration [ ] = [ ] ;
1124+ let hasAny = false ;
1125+ for ( const p of initializer . parameters ) {
1126+ if ( p . type ) {
1127+ parameters . push ( p ) ;
1128+ }
1129+ else {
1130+ const paramType = checker . getTypeAtLocation ( p ) ;
1131+ if ( paramType === checker . getAnyType ( ) ) hasAny = true ;
1132+
1133+ parameters . push ( updateParameter ( p ,
1134+ p . decorators , p . modifiers , p . dotDotDotToken ,
1135+ p . name , p . questionToken , p . type || checker . typeToTypeNode ( paramType , scope , NodeBuilderFlags . NoTruncation ) , p . initializer ) ) ;
1136+ }
1137+ }
1138+ // If a parameter was inferred as any we skip adding function parameters at all.
1139+ // Turning an implicit any (which under common settings is a error) to an explicit
1140+ // is probably actually a worse refactor outcome.
1141+ if ( hasAny ) return { variableType, initializer } ;
1142+ variableType = undefined ;
1143+ if ( isArrowFunction ( initializer ) ) {
1144+ initializer = updateArrowFunction ( initializer , node . modifiers , initializer . typeParameters ,
1145+ parameters ,
1146+ initializer . type || checker . typeToTypeNode ( functionSignature . getReturnType ( ) , scope , NodeBuilderFlags . NoTruncation ) ,
1147+ initializer . equalsGreaterThanToken ,
1148+ initializer . body ) ;
1149+ }
1150+ else {
1151+ if ( functionSignature && ! ! functionSignature . thisParameter ) {
1152+ const firstParameter = firstOrUndefined ( parameters ) ;
1153+ // If the function signature has a this parameter and if the first defined parameter is not the this parameter, we must add it
1154+ // Note: If this parameter was already there, it would have been previously updated with the type if not type was present
1155+ if ( ( ! firstParameter || ( isIdentifier ( firstParameter . name ) && firstParameter . name . escapedText !== "this" ) ) ) {
1156+ const thisType = checker . getTypeOfSymbolAtLocation ( functionSignature . thisParameter , node ) ;
1157+ parameters . splice ( 0 , 0 , createParameter (
1158+ /* decorators */ undefined ,
1159+ /* modifiers */ undefined ,
1160+ /* dotDotDotToken */ undefined ,
1161+ "this" ,
1162+ /* questionToken */ undefined ,
1163+ checker . typeToTypeNode ( thisType , scope , NodeBuilderFlags . NoTruncation )
1164+ ) ) ;
1165+ }
1166+ }
1167+ initializer = updateFunctionExpression ( initializer , node . modifiers , initializer . asteriskToken ,
1168+ initializer . name , initializer . typeParameters ,
1169+ parameters ,
1170+ initializer . type || checker . typeToTypeNode ( functionSignature . getReturnType ( ) , scope , NodeBuilderFlags . NoTruncation ) ,
1171+ initializer . body ) ;
1172+ }
1173+ return { variableType, initializer } ;
1174+ }
11051175 }
11061176
11071177 function getContainingVariableDeclarationIfInList ( node : Node , scope : Scope ) {
0 commit comments