@@ -214,6 +214,7 @@ namespace ts.textChanges {
214
214
private readonly newFiles : { readonly oldFile : SourceFile , readonly fileName : string , readonly statements : ReadonlyArray < Statement > } [ ] = [ ] ;
215
215
private readonly deletedNodesInLists = new NodeSet ( ) ; // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
216
216
private readonly classesWithNodesInsertedAtStart = createMap < ClassDeclaration > ( ) ; // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
217
+ private readonly deletedDeclarations : { readonly sourceFile : SourceFile , readonly node : Node } [ ] = [ ] ;
217
218
218
219
public static fromContext ( context : TextChangesContext ) : ChangeTracker {
219
220
return new ChangeTracker ( getNewLineOrDefaultFromHost ( context . host , context . formatContext . options ) , context . formatContext ) ;
@@ -233,6 +234,10 @@ namespace ts.textChanges {
233
234
return this ;
234
235
}
235
236
237
+ deleteDeclaration ( sourceFile : SourceFile , node : Node ) : void {
238
+ this . deletedDeclarations . push ( { sourceFile, node } ) ;
239
+ }
240
+
236
241
/** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
237
242
public deleteNode ( sourceFile : SourceFile , node : Node , options : ConfigurableStartEnd = { } ) {
238
243
const startPosition = getAdjustedStartPosition ( sourceFile , node , options , Position . FullStart ) ;
@@ -699,13 +704,22 @@ namespace ts.textChanges {
699
704
} ) ;
700
705
}
701
706
707
+ private finishDeleteDeclarations ( ) : void {
708
+ for ( const { sourceFile, node } of this . deletedDeclarations ) {
709
+ if ( ! this . deletedDeclarations . some ( d => d . sourceFile === sourceFile && rangeContainsRangeExclusive ( d . node , node ) ) ) {
710
+ deleteDeclaration . deleteDeclaration ( this , sourceFile , node ) ;
711
+ }
712
+ }
713
+ }
714
+
702
715
/**
703
716
* Note: after calling this, the TextChanges object must be discarded!
704
717
* @param validate only for tests
705
718
* The reason we must validate as part of this method is that `getNonFormattedText` changes the node's positions,
706
719
* so we can only call this once and can't get the non-formatted text separately.
707
720
*/
708
721
public getChanges ( validate ?: ValidateNonFormattedText ) : FileTextChanges [ ] {
722
+ this . finishDeleteDeclarations ( ) ;
709
723
this . finishClassesWithNodesInsertedAtStart ( ) ;
710
724
this . finishTrailingCommaAfterDeletingNodesInList ( ) ;
711
725
const changes = changesToText . getTextChangesFromChanges ( this . changes , this . newLineCharacter , this . formatContext , validate ) ;
@@ -1021,4 +1035,170 @@ namespace ts.textChanges {
1021
1035
return ( isPropertySignature ( a ) || isPropertyDeclaration ( a ) ) && isClassOrTypeElement ( b ) && b . name ! . kind === SyntaxKind . ComputedPropertyName
1022
1036
|| isStatementButNotDeclaration ( a ) && isStatementButNotDeclaration ( b ) ; // TODO: only if b would start with a `(` or `[`
1023
1037
}
1038
+
1039
+ namespace deleteDeclaration {
1040
+ export function deleteDeclaration ( changes : ChangeTracker , sourceFile : SourceFile , node : Node ) : void {
1041
+ switch ( node . kind ) {
1042
+ case SyntaxKind . Parameter : {
1043
+ const oldFunction = node . parent ;
1044
+ if ( isArrowFunction ( oldFunction ) && oldFunction . parameters . length === 1 ) {
1045
+ // Lambdas with exactly one parameter are special because, after removal, there
1046
+ // must be an empty parameter list (i.e. `()`) and this won't necessarily be the
1047
+ // case if the parameter is simply removed (e.g. in `x => 1`).
1048
+ const newFunction = updateArrowFunction (
1049
+ oldFunction ,
1050
+ oldFunction . modifiers ,
1051
+ oldFunction . typeParameters ,
1052
+ /*parameters*/ undefined ! , // TODO: GH#18217
1053
+ oldFunction . type ,
1054
+ oldFunction . equalsGreaterThanToken ,
1055
+ oldFunction . body ) ;
1056
+
1057
+ // Drop leading and trailing trivia of the new function because we're only going
1058
+ // to replace the span (vs the full span) of the old function - the old leading
1059
+ // and trailing trivia will remain.
1060
+ suppressLeadingAndTrailingTrivia ( newFunction ) ;
1061
+
1062
+ changes . replaceNode ( sourceFile , oldFunction , newFunction ) ;
1063
+ }
1064
+ else {
1065
+ changes . deleteNodeInList ( sourceFile , node ) ;
1066
+ }
1067
+ break ;
1068
+ }
1069
+
1070
+ case SyntaxKind . ImportDeclaration :
1071
+ changes . deleteNode ( sourceFile , node ) ;
1072
+ break ;
1073
+
1074
+ case SyntaxKind . BindingElement :
1075
+ const pattern = ( node as BindingElement ) . parent ;
1076
+ const preserveComma = pattern . kind === SyntaxKind . ArrayBindingPattern && node !== last ( pattern . elements ) ;
1077
+ if ( preserveComma ) {
1078
+ changes . deleteNode ( sourceFile , node ) ;
1079
+ }
1080
+ else {
1081
+ changes . deleteNodeInList ( sourceFile , node ) ;
1082
+ }
1083
+ break ;
1084
+
1085
+ case SyntaxKind . VariableDeclaration :
1086
+ deleteVariableDeclaration ( changes , sourceFile , node as VariableDeclaration ) ;
1087
+ break ;
1088
+
1089
+ case SyntaxKind . TypeParameter : {
1090
+ const typeParameters = getEffectiveTypeParameterDeclarations ( < DeclarationWithTypeParameters > node . parent ) ;
1091
+ if ( typeParameters . length === 1 ) {
1092
+ const { pos, end } = cast ( typeParameters , isNodeArray ) ;
1093
+ const previousToken = getTokenAtPosition ( sourceFile , pos - 1 ) ;
1094
+ const nextToken = getTokenAtPosition ( sourceFile , end ) ;
1095
+ Debug . assert ( previousToken . kind === SyntaxKind . LessThanToken ) ;
1096
+ Debug . assert ( nextToken . kind === SyntaxKind . GreaterThanToken ) ;
1097
+
1098
+ changes . deleteNodeRange ( sourceFile , previousToken , nextToken ) ;
1099
+ }
1100
+ else {
1101
+ changes . deleteNodeInList ( sourceFile , node ) ;
1102
+ }
1103
+ break ;
1104
+ }
1105
+
1106
+ case SyntaxKind . ImportSpecifier :
1107
+ const namedImports = ( node as ImportSpecifier ) . parent ;
1108
+ if ( namedImports . elements . length === 1 ) {
1109
+ deleteImportBinding ( changes , sourceFile , namedImports ) ;
1110
+ }
1111
+ else {
1112
+ changes . deleteNodeInList ( sourceFile , node ) ;
1113
+ }
1114
+ break ;
1115
+
1116
+ case SyntaxKind . NamespaceImport :
1117
+ deleteImportBinding ( changes , sourceFile , node as NamespaceImport ) ;
1118
+ break ;
1119
+
1120
+ default :
1121
+ if ( isImportClause ( node . parent ) && node . parent . name === node ) {
1122
+ deleteDefaultImport ( changes , sourceFile , node . parent ) ;
1123
+ }
1124
+ else if ( isCallLikeExpression ( node . parent ) ) {
1125
+ changes . deleteNodeInList ( sourceFile , node ) ;
1126
+ }
1127
+ else {
1128
+ changes . deleteNode ( sourceFile , node ) ;
1129
+ }
1130
+ }
1131
+ }
1132
+
1133
+ function deleteDefaultImport ( changes : ChangeTracker , sourceFile : SourceFile , importClause : ImportClause ) : void {
1134
+ if ( ! importClause . namedBindings ) {
1135
+ // Delete the whole import
1136
+ changes . deleteNode ( sourceFile , importClause . parent ) ;
1137
+ }
1138
+ else {
1139
+ // import |d,| * as ns from './file'
1140
+ const start = importClause . name ! . getStart ( sourceFile ) ;
1141
+ const nextToken = getTokenAtPosition ( sourceFile , importClause . name ! . end ) ;
1142
+ if ( nextToken && nextToken . kind === SyntaxKind . CommaToken ) {
1143
+ // shift first non-whitespace position after comma to the start position of the node
1144
+ const end = skipTrivia ( sourceFile . text , nextToken . end , /*stopAfterLineBreaks*/ false , /*stopAtComments*/ true ) ;
1145
+ changes . deleteRange ( sourceFile , { pos : start , end } ) ;
1146
+ }
1147
+ else {
1148
+ changes . deleteNode ( sourceFile , importClause . name ! ) ;
1149
+ }
1150
+ }
1151
+ }
1152
+
1153
+ function deleteImportBinding ( changes : ChangeTracker , sourceFile : SourceFile , node : NamedImportBindings ) : void {
1154
+ if ( node . parent . name ) {
1155
+ // Delete named imports while preserving the default import
1156
+ // import d|, * as ns| from './file'
1157
+ // import d|, { a }| from './file'
1158
+ const previousToken = Debug . assertDefined ( getTokenAtPosition ( sourceFile , node . pos - 1 ) ) ;
1159
+ changes . deleteRange ( sourceFile , { pos : previousToken . getStart ( sourceFile ) , end : node . end } ) ;
1160
+ }
1161
+ else {
1162
+ // Delete the entire import declaration
1163
+ // |import * as ns from './file'|
1164
+ // |import { a } from './file'|
1165
+ const importDecl = getAncestor ( node , SyntaxKind . ImportDeclaration ) ! ;
1166
+ changes . deleteNode ( sourceFile , importDecl ) ;
1167
+ }
1168
+ }
1169
+
1170
+ function deleteVariableDeclaration ( changes : ChangeTracker , sourceFile : SourceFile , node : VariableDeclaration ) : void {
1171
+ const { parent } = node ;
1172
+
1173
+ if ( parent . kind === SyntaxKind . CatchClause ) {
1174
+ // TODO: There's currently no unused diagnostic for this, could be a suggestion
1175
+ changes . deleteNodeRange ( sourceFile , findChildOfKind ( parent , SyntaxKind . OpenParenToken , sourceFile ) ! , findChildOfKind ( parent , SyntaxKind . CloseParenToken , sourceFile ) ! ) ;
1176
+ return ;
1177
+ }
1178
+
1179
+ if ( parent . declarations . length !== 1 ) {
1180
+ changes . deleteNodeInList ( sourceFile , node ) ;
1181
+ return ;
1182
+ }
1183
+
1184
+ const gp = parent . parent ;
1185
+ switch ( gp . kind ) {
1186
+ case SyntaxKind . ForOfStatement :
1187
+ case SyntaxKind . ForInStatement :
1188
+ changes . replaceNode ( sourceFile , node , createObjectLiteral ( ) ) ;
1189
+ break ;
1190
+
1191
+ case SyntaxKind . ForStatement :
1192
+ changes . deleteNode ( sourceFile , parent ) ;
1193
+ break ;
1194
+
1195
+ case SyntaxKind . VariableStatement :
1196
+ changes . deleteNode ( sourceFile , gp ) ;
1197
+ break ;
1198
+
1199
+ default :
1200
+ Debug . assertNever ( gp ) ;
1201
+ }
1202
+ }
1203
+ }
1024
1204
}
0 commit comments