@@ -1006,11 +1006,14 @@ namespace ts.refactor.extractSymbol {
1006
1006
const localNameText = getUniqueName ( isClassLike ( scope ) ? "newProperty" : "newLocal" , file ) ;
1007
1007
const isJS = isInJSFile ( scope ) ;
1008
1008
1009
- const variableType = isJS || ! checker . isContextSensitive ( node )
1009
+ let variableType = isJS || ! checker . isContextSensitive ( node )
1010
1010
? undefined
1011
1011
: checker . typeToTypeNode ( checker . getContextualType ( node ) ! , scope , NodeBuilderFlags . NoTruncation ) ; // TODO: GH#18217
1012
1012
1013
- const initializer = transformConstantInitializer ( node , substitutions ) ;
1013
+ let initializer = transformConstantInitializer ( node , substitutions ) ;
1014
+
1015
+ ( { variableType, initializer } = transformFunctionInitializerAndType ( variableType , initializer ) ) ;
1016
+
1014
1017
suppressLeadingAndTrailingTrivia ( initializer ) ;
1015
1018
1016
1019
const changeTracker = textChanges . ChangeTracker . fromContext ( context ) ;
@@ -1102,6 +1105,73 @@ namespace ts.refactor.extractSymbol {
1102
1105
const renameFilename = node . getSourceFile ( ) . fileName ;
1103
1106
const renameLocation = getRenameLocation ( edits , renameFilename , localNameText , /*isDeclaredBeforeUse*/ true ) ;
1104
1107
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 noting 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
+ }
1105
1175
}
1106
1176
1107
1177
function getContainingVariableDeclarationIfInList ( node : Node , scope : Scope ) {
0 commit comments