@@ -890,33 +890,56 @@ namespace ts.refactor.extractSymbol {
890
890
createIdentifier ( localNameText ) ) ;
891
891
892
892
// Declare
893
- const minInsertionPos = node . end ;
894
- const nodeToInsertBefore = getNodeToInsertConstantBefore ( minInsertionPos , scope ) ;
893
+ const maxInsertionPos = node . pos ;
894
+ const nodeToInsertBefore = getNodeToInsertPropertyBefore ( maxInsertionPos , scope ) ;
895
895
changeTracker . insertNodeBefore ( context . file , nodeToInsertBefore , newVariable , { suffix : context . newLineCharacter + context . newLineCharacter } ) ;
896
896
897
897
// Consume
898
898
changeTracker . replaceNodeWithNodes ( context . file , node , [ localReference ] , { nodeSeparator : context . newLineCharacter } ) ;
899
899
}
900
900
else {
901
- const newVariable = createVariableStatement (
902
- /*modifiers*/ undefined ,
903
- createVariableDeclarationList (
904
- [ createVariableDeclaration ( localNameText , variableType , initializer ) ] ,
905
- NodeFlags . Const ) ) ;
901
+ const newVariableDeclaration = createVariableDeclaration ( localNameText , variableType , initializer ) ;
902
+
903
+ // If the node is part of an initializer in a list of variable declarations, insert a new
904
+ // variable declaration into the list (in case it depends on earlier ones).
905
+ // CONSIDER: If the declaration list isn't const, we might want to split it into multiple
906
+ // lists so that the newly extracted one can be const.
907
+ const oldVariableDeclaration = getContainingVariableDeclarationIfInList ( node , scope ) ;
908
+ if ( oldVariableDeclaration ) {
909
+ // Declare
910
+ // CONSIDER: could detect that each is on a separate line
911
+ changeTracker . insertNodeAt ( context . file , oldVariableDeclaration . getStart ( ) , newVariableDeclaration , { suffix : ", " } ) ;
906
912
907
- // If the parent is an expression statement, replace the statement with the declaration
908
- if ( node . parent . kind === SyntaxKind . ExpressionStatement ) {
909
- changeTracker . replaceNodeWithNodes ( context . file , node . parent , [ newVariable ] , { nodeSeparator : context . newLineCharacter } ) ;
913
+ // Consume
914
+ const localReference = createIdentifier ( localNameText ) ;
915
+ changeTracker . replaceRange ( context . file , { pos : node . getStart ( ) , end : node . end } , localReference ) ;
916
+ }
917
+ else if ( node . parent . kind === SyntaxKind . ExpressionStatement ) {
918
+ // If the parent is an expression statement, replace the statement with the declaration.
919
+ const newVariableStatement = createVariableStatement (
920
+ /*modifiers*/ undefined ,
921
+ createVariableDeclarationList ( [ newVariableDeclaration ] , NodeFlags . Const ) ) ;
922
+ changeTracker . replaceNodeWithNodes ( context . file , node . parent , [ newVariableStatement ] , { nodeSeparator : context . newLineCharacter } ) ;
910
923
}
911
924
else {
925
+ const newVariableStatement = createVariableStatement (
926
+ /*modifiers*/ undefined ,
927
+ createVariableDeclarationList ( [ newVariableDeclaration ] , NodeFlags . Const ) ) ;
928
+
912
929
// Declare
913
- const minInsertionPos = node . end ;
914
- const nodeToInsertBefore = getNodeToInsertConstantBefore ( minInsertionPos , scope ) ;
915
- changeTracker . insertNodeBefore ( context . file , nodeToInsertBefore , newVariable , { suffix : context . newLineCharacter + context . newLineCharacter } ) ;
930
+ const nodeToInsertBefore = getNodeToInsertConstantBefore ( node , scope ) ;
931
+ if ( nodeToInsertBefore . pos === 0 ) {
932
+ // If we're at the beginning of the file, we need to take care not to insert before header comments
933
+ // (e.g. copyright, triple-slash references).
934
+ changeTracker . insertNodeAt ( context . file , nodeToInsertBefore . getStart ( ) , newVariableStatement , { suffix : context . newLineCharacter + context . newLineCharacter } ) ;
935
+ }
936
+ else {
937
+ changeTracker . insertNodeBefore ( context . file , nodeToInsertBefore , newVariableStatement , { suffix : context . newLineCharacter + context . newLineCharacter } ) ;
938
+ }
916
939
917
940
// Consume
918
941
const localReference = createIdentifier ( localNameText ) ;
919
- changeTracker . replaceNodeWithNodes ( context . file , node , [ localReference ] , { nodeSeparator : context . newLineCharacter } ) ;
942
+ changeTracker . replaceRange ( context . file , { pos : node . getStart ( ) , end : node . end } , localReference ) ;
920
943
}
921
944
}
922
945
@@ -927,6 +950,22 @@ namespace ts.refactor.extractSymbol {
927
950
return { renameFilename, renameLocation, edits } ;
928
951
}
929
952
953
+ function getContainingVariableDeclarationIfInList ( node : Node , scope : Scope ) {
954
+ let prevNode = undefined ;
955
+ while ( node !== undefined && node !== scope ) {
956
+ if ( isVariableDeclaration ( node ) &&
957
+ node . initializer === prevNode &&
958
+ isVariableDeclarationList ( node . parent ) &&
959
+ node . parent . declarations . length > 1 ) {
960
+
961
+ return node ;
962
+ }
963
+
964
+ prevNode = node ;
965
+ node = node . parent ;
966
+ }
967
+ }
968
+
930
969
/**
931
970
* @return The index of the (only) reference to the extracted symbol. We want the cursor
932
971
* to be on the reference, rather than the declaration, because it's closer to where the
@@ -1109,26 +1148,61 @@ namespace ts.refactor.extractSymbol {
1109
1148
child . pos >= minPos && isFunctionLikeDeclaration ( child ) && ! isConstructorDeclaration ( child ) ) ;
1110
1149
}
1111
1150
1112
- // TODO (acasey): need to dig into nested statements
1113
- // TODO (acasey): don't insert before pinned comments, directives, or triple-slash references
1114
- function getNodeToInsertConstantBefore ( maxPos : number , scope : Scope ) : Node {
1115
- const children = getStatementsOrClassElements ( scope ) ;
1116
- Debug . assert ( children . length > 0 ) ; // There must be at least one child, since we extracted from one.
1151
+ function getNodeToInsertPropertyBefore ( maxPos : number , scope : ClassLikeDeclaration ) : Node {
1152
+ const members = scope . members ;
1153
+ Debug . assert ( members . length > 0 ) ; // There must be at least one child, since we extracted from one.
1154
+
1155
+ let prevMember : ClassElement | undefined = undefined ;
1156
+ let allProperties = true ;
1157
+ for ( const member of members ) {
1158
+ if ( member . pos > maxPos ) {
1159
+ return prevMember || members [ 0 ] ;
1160
+ }
1161
+ if ( allProperties && ! isPropertyDeclaration ( member ) ) {
1162
+ // If it is non-vacuously true that all preceding members are properties,
1163
+ // insert before the current member (i.e. at the end of the list of properties).
1164
+ if ( prevMember !== undefined ) {
1165
+ return member ;
1166
+ }
1117
1167
1118
- const isClassLikeScope = isClassLike ( scope ) ;
1119
- let prevChild : Statement | ClassElement | undefined = undefined ;
1120
- for ( const child of children ) {
1121
- if ( child . pos >= maxPos ) {
1122
- break ;
1168
+ allProperties = false ;
1123
1169
}
1124
- prevChild = child ;
1125
- if ( isClassLikeScope && ! isPropertyDeclaration ( child ) ) {
1126
- break ;
1170
+ prevMember = member ;
1171
+ }
1172
+
1173
+ Debug . assert ( prevMember !== undefined ) ; // If the loop didn't return, then it did set prevMember.
1174
+ return prevMember ;
1175
+ }
1176
+
1177
+ function getNodeToInsertConstantBefore ( node : Node , scope : Scope ) : Node {
1178
+ Debug . assert ( ! isClassLike ( scope ) ) ;
1179
+
1180
+ let prevScope : Scope | undefined = undefined ;
1181
+ for ( let curr = node ; curr !== scope ; curr = curr . parent ) {
1182
+ if ( isScope ( curr ) ) {
1183
+ prevScope = curr ;
1127
1184
}
1128
1185
}
1129
1186
1130
- Debug . assert ( prevChild !== undefined ) ;
1131
- return prevChild ;
1187
+ for ( let curr = ( prevScope || node ) . parent ; ; curr = curr . parent ) {
1188
+ if ( isBlockLike ( curr ) ) {
1189
+ let prevStatement = undefined ;
1190
+ for ( const statement of curr . statements ) {
1191
+ if ( statement . pos > node . pos ) {
1192
+ break ;
1193
+ }
1194
+ prevStatement = statement ;
1195
+ }
1196
+ // There must be at least one statement since we started in one.
1197
+ Debug . assert ( prevStatement !== undefined ) ;
1198
+ return prevStatement ;
1199
+ }
1200
+
1201
+ if ( curr === scope ) {
1202
+ Debug . fail ( "Didn't encounter a block-like before encountering scope" ) ;
1203
+ break ;
1204
+ }
1205
+ }
1132
1206
}
1133
1207
1134
1208
function getPropertyAssignmentsForWrites ( writes : ReadonlyArray < UsageEntry > ) : ShorthandPropertyAssignment [ ] {
0 commit comments