@@ -610,7 +610,7 @@ namespace ts.refactor.extractMethod {
610
610
export function extractFunctionInScope (
611
611
node : Statement | Expression | Block ,
612
612
scope : Scope ,
613
- { usages : usagesInScope , substitutions } : ScopeUsages ,
613
+ { usages : usagesInScope , typeParameterUsages , substitutions } : ScopeUsages ,
614
614
range : TargetRange ,
615
615
context : RefactorContext ) : ExtractResultForScope {
616
616
@@ -652,7 +652,18 @@ namespace ts.refactor.extractMethod {
652
652
callArguments . push ( createIdentifier ( name ) ) ;
653
653
} ) ;
654
654
655
- // Provide explicit return types for contexutally-typed functions
655
+ const typeParametersAndDeclarations = arrayFrom ( typeParameterUsages . values ( ) ) . map ( type => ( { type, declaration : getFirstDeclaration ( type ) } ) ) ;
656
+ const sortedTypeParametersAndDeclarations = typeParametersAndDeclarations . sort ( compareTypesByDeclarationOrder ) ;
657
+
658
+ const typeParameters : ReadonlyArray < TypeParameterDeclaration > = sortedTypeParametersAndDeclarations . map ( t => t . declaration as TypeParameterDeclaration ) ;
659
+
660
+ // Strictly speaking, we should check whether each name actually binds to the appropriate type
661
+ // parameter. In cases of shadowing, they may not.
662
+ const callTypeArguments : ReadonlyArray < TypeNode > | undefined = typeParameters . length > 0
663
+ ? typeParameters . map ( decl => createTypeReferenceNode ( decl . name , /*typeArguments*/ undefined ) )
664
+ : undefined ;
665
+
666
+ // Provide explicit return types for contextually-typed functions
656
667
// to avoid problems when there are literal types present
657
668
if ( isExpression ( node ) && ! isJS ) {
658
669
const contextualType = checker . getContextualType ( node ) ;
@@ -677,7 +688,7 @@ namespace ts.refactor.extractMethod {
677
688
range . facts & RangeFacts . IsGenerator ? createToken ( SyntaxKind . AsteriskToken ) : undefined ,
678
689
functionName ,
679
690
/*questionToken*/ undefined ,
680
- /* typeParameters*/ [ ] ,
691
+ typeParameters ,
681
692
parameters ,
682
693
returnType ,
683
694
body
@@ -689,7 +700,7 @@ namespace ts.refactor.extractMethod {
689
700
range . facts & RangeFacts . IsAsyncFunction ? [ createToken ( SyntaxKind . AsyncKeyword ) ] : undefined ,
690
701
range . facts & RangeFacts . IsGenerator ? createToken ( SyntaxKind . AsteriskToken ) : undefined ,
691
702
functionName ,
692
- /* typeParameters*/ [ ] ,
703
+ typeParameters ,
693
704
parameters ,
694
705
returnType ,
695
706
body
@@ -704,7 +715,7 @@ namespace ts.refactor.extractMethod {
704
715
// replace range with function call
705
716
let call : Expression = createCall (
706
717
isClassLike ( scope ) ? createPropertyAccess ( range . facts & RangeFacts . InStaticRegion ? createIdentifier ( scope . name . getText ( ) ) : createThis ( ) , functionReference ) : functionReference ,
707
- /*typeArguments*/ undefined ,
718
+ callTypeArguments , // Note that no attempt is made to take advantage of type argument inference
708
719
callArguments ) ;
709
720
if ( range . facts & RangeFacts . IsGenerator ) {
710
721
call = createYield ( createToken ( SyntaxKind . AsteriskToken ) , call ) ;
@@ -774,6 +785,51 @@ namespace ts.refactor.extractMethod {
774
785
changes : changeTracker . getChanges ( )
775
786
} ;
776
787
788
+ function getFirstDeclaration ( type : Type ) : Declaration | undefined {
789
+ let firstDeclaration = undefined ;
790
+
791
+ const symbol = type . symbol ;
792
+ if ( symbol && symbol . declarations ) {
793
+ for ( const declaration of symbol . declarations ) {
794
+ if ( firstDeclaration === undefined || declaration . pos < firstDeclaration . pos ) {
795
+ firstDeclaration = declaration ;
796
+ }
797
+ }
798
+ }
799
+
800
+ return firstDeclaration ;
801
+ }
802
+
803
+ function compareTypesByDeclarationOrder (
804
+ { type : type1 , declaration : declaration1 } : { type : Type , declaration ?: Declaration } ,
805
+ { type : type2 , declaration : declaration2 } : { type : Type , declaration ?: Declaration } ) {
806
+
807
+ if ( declaration1 ) {
808
+ if ( declaration2 ) {
809
+ const positionDiff = declaration1 . pos - declaration2 . pos ;
810
+ if ( positionDiff !== 0 ) {
811
+ return positionDiff ;
812
+ }
813
+ }
814
+ else {
815
+ return 1 ; // Sort undeclared type parameters to the front.
816
+ }
817
+ }
818
+ else if ( declaration2 ) {
819
+ return - 1 ; // Sort undeclared type parameters to the front.
820
+ }
821
+
822
+ const name1 = type1 . symbol ? type1 . symbol . getName ( ) : "" ;
823
+ const name2 = type2 . symbol ? type2 . symbol . getName ( ) : "" ;
824
+ const nameDiff = compareStrings ( name1 , name2 ) ;
825
+ if ( nameDiff !== 0 ) {
826
+ return nameDiff ;
827
+ }
828
+
829
+ // IDs are guaranteed to be unique, so this ensures a total ordering.
830
+ return type1 . id - type2 . id ;
831
+ }
832
+
777
833
function getPropertyAssignmentsForWrites ( writes : UsageEntry [ ] ) {
778
834
return writes . map ( w => createShorthandPropertyAssignment ( w . symbol . name ) ) ;
779
835
}
@@ -867,6 +923,7 @@ namespace ts.refactor.extractMethod {
867
923
868
924
export interface ScopeUsages {
869
925
usages : Map < UsageEntry > ;
926
+ typeParameterUsages : Map < TypeParameter > ; // Key is type ID
870
927
substitutions : Map < Node > ;
871
928
}
872
929
@@ -877,23 +934,58 @@ namespace ts.refactor.extractMethod {
877
934
sourceFile : SourceFile ,
878
935
checker : TypeChecker ) {
879
936
937
+ const allTypeParameterUsages = createMap < TypeParameter > ( ) ; // Key is type ID
880
938
const usagesPerScope : ScopeUsages [ ] = [ ] ;
881
939
const substitutionsPerScope : Map < Node > [ ] = [ ] ;
882
940
const errorsPerScope : Diagnostic [ ] [ ] = [ ] ;
883
941
const visibleDeclarationsInExtractedRange : Symbol [ ] = [ ] ;
884
942
885
943
// initialize results
886
944
for ( const _ of scopes ) {
887
- usagesPerScope . push ( { usages : createMap < UsageEntry > ( ) , substitutions : createMap < Expression > ( ) } ) ;
945
+ usagesPerScope . push ( { usages : createMap < UsageEntry > ( ) , typeParameterUsages : createMap < TypeParameter > ( ) , substitutions : createMap < Expression > ( ) } ) ;
888
946
substitutionsPerScope . push ( createMap < Expression > ( ) ) ;
889
947
errorsPerScope . push ( [ ] ) ;
890
948
}
891
949
const seenUsages = createMap < Usage > ( ) ;
892
950
const target = isReadonlyArray ( targetRange . range ) ? createBlock ( < Statement [ ] > targetRange . range ) : targetRange . range ;
893
951
const containingLexicalScopeOfExtraction = isBlockScope ( scopes [ 0 ] , scopes [ 0 ] . parent ) ? scopes [ 0 ] : getEnclosingBlockScopeContainer ( scopes [ 0 ] ) ;
894
952
953
+ const unmodifiedNode = isReadonlyArray ( targetRange . range ) ? targetRange . range [ 0 ] : targetRange . range ;
954
+ const inGenericContext = isInGenericContext ( unmodifiedNode ) ;
955
+
895
956
collectUsages ( target ) ;
896
957
958
+ if ( allTypeParameterUsages . size > 0 ) {
959
+ const seenTypeParameterUsages = createMap < TypeParameter > ( ) ; // Key is type ID
960
+
961
+ let i = 0 ;
962
+ for ( let curr : Node = unmodifiedNode ; curr !== undefined && i < scopes . length ; curr = curr . parent ) {
963
+ if ( curr === scopes [ i ] ) {
964
+ // Copy current contents of seenTypeParameterUsages into scope.
965
+ seenTypeParameterUsages . forEach ( ( typeParameter , id ) => {
966
+ usagesPerScope [ i ] . typeParameterUsages . set ( id , typeParameter ) ;
967
+ } ) ;
968
+
969
+ i ++ ;
970
+ }
971
+
972
+ // Note that we add the current node's type parameters *after* updating the corresponding scope.
973
+ if ( isDeclarationWithTypeParameters ( curr ) && curr . typeParameters ) {
974
+ for ( const typeParameterDecl of curr . typeParameters ) {
975
+ const typeParameter = checker . getTypeAtLocation ( typeParameterDecl ) as TypeParameter ;
976
+ if ( allTypeParameterUsages . has ( typeParameter . id . toString ( ) ) ) {
977
+ seenTypeParameterUsages . set ( typeParameter . id . toString ( ) , typeParameter ) ;
978
+ }
979
+ }
980
+ }
981
+ }
982
+
983
+ // If we didn't get through all the scopes, then there were some that weren't in our
984
+ // parent chain (impossible at time of writing). A conservative solution would be to
985
+ // copy allTypeParameterUsages into all remaining scopes.
986
+ Debug . assert ( i === scopes . length ) ;
987
+ }
988
+
897
989
for ( let i = 0 ; i < scopes . length ; i ++ ) {
898
990
let hasWrite = false ;
899
991
let readonlyClassPropertyWrite : Declaration | undefined = undefined ;
@@ -912,7 +1004,7 @@ namespace ts.refactor.extractMethod {
912
1004
errorsPerScope [ i ] . push ( createDiagnosticForNode ( targetRange . range , Messages . CannotCombineWritesAndReturns ) ) ;
913
1005
}
914
1006
else if ( readonlyClassPropertyWrite && i > 0 ) {
915
- errorsPerScope [ i ] . push ( createDiagnosticForNode ( readonlyClassPropertyWrite , Messages . CannotCombineWritesAndReturns ) ) ;
1007
+ errorsPerScope [ i ] . push ( createDiagnosticForNode ( readonlyClassPropertyWrite , Messages . CannotExtractReadonlyPropertyInitializerOutsideConstructor ) ) ;
916
1008
}
917
1009
}
918
1010
@@ -924,7 +1016,36 @@ namespace ts.refactor.extractMethod {
924
1016
925
1017
return { target, usagesPerScope, errorsPerScope } ;
926
1018
1019
+ function hasTypeParameters ( node : Node ) {
1020
+ return isDeclarationWithTypeParameters ( node ) &&
1021
+ node . typeParameters !== undefined &&
1022
+ node . typeParameters . length > 0 ;
1023
+ }
1024
+
1025
+ function isInGenericContext ( node : Node ) {
1026
+ for ( ; node ; node = node . parent ) {
1027
+ if ( hasTypeParameters ( node ) ) {
1028
+ return true ;
1029
+ }
1030
+ }
1031
+
1032
+ return false ;
1033
+ }
1034
+
927
1035
function collectUsages ( node : Node , valueUsage = Usage . Read ) {
1036
+ if ( inGenericContext ) {
1037
+ const type = checker . getTypeAtLocation ( node ) ;
1038
+
1039
+ const symbolWalker = checker . getSymbolWalker ( ) ;
1040
+ const { visitedTypes} = symbolWalker . walkType ( type ) ;
1041
+
1042
+ for ( const visitedType of visitedTypes ) {
1043
+ if ( visitedType . flags & TypeFlags . TypeParameter ) {
1044
+ allTypeParameterUsages . set ( visitedType . id . toString ( ) , visitedType as TypeParameter ) ;
1045
+ }
1046
+ }
1047
+ }
1048
+
928
1049
if ( isDeclaration ( node ) && node . symbol ) {
929
1050
visibleDeclarationsInExtractedRange . push ( node . symbol ) ;
930
1051
}
0 commit comments