@@ -214,6 +214,7 @@ namespace ts.textChanges {
214214 private readonly newFiles : { readonly oldFile : SourceFile , readonly fileName : string , readonly statements : ReadonlyArray < Statement > } [ ] = [ ] ;
215215 private readonly deletedNodesInLists = new NodeSet ( ) ; // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
216216 private readonly classesWithNodesInsertedAtStart = createMap < ClassDeclaration > ( ) ; // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
217+ private readonly deletedDeclarations : { readonly sourceFile : SourceFile , readonly node : Node } [ ] = [ ] ;
217218
218219 public static fromContext ( context : TextChangesContext ) : ChangeTracker {
219220 return new ChangeTracker ( getNewLineOrDefaultFromHost ( context . host , context . formatContext . options ) , context . formatContext ) ;
@@ -233,6 +234,10 @@ namespace ts.textChanges {
233234 return this ;
234235 }
235236
237+ deleteDeclaration ( sourceFile : SourceFile , node : Node ) : void {
238+ this . deletedDeclarations . push ( { sourceFile, node } ) ;
239+ }
240+
236241 /** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */
237242 public deleteNode ( sourceFile : SourceFile , node : Node , options : ConfigurableStartEnd = { } ) {
238243 const startPosition = getAdjustedStartPosition ( sourceFile , node , options , Position . FullStart ) ;
@@ -699,13 +704,22 @@ namespace ts.textChanges {
699704 } ) ;
700705 }
701706
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+
702715 /**
703716 * Note: after calling this, the TextChanges object must be discarded!
704717 * @param validate only for tests
705718 * The reason we must validate as part of this method is that `getNonFormattedText` changes the node's positions,
706719 * so we can only call this once and can't get the non-formatted text separately.
707720 */
708721 public getChanges ( validate ?: ValidateNonFormattedText ) : FileTextChanges [ ] {
722+ this . finishDeleteDeclarations ( ) ;
709723 this . finishClassesWithNodesInsertedAtStart ( ) ;
710724 this . finishTrailingCommaAfterDeletingNodesInList ( ) ;
711725 const changes = changesToText . getTextChangesFromChanges ( this . changes , this . newLineCharacter , this . formatContext , validate ) ;
@@ -1021,4 +1035,170 @@ namespace ts.textChanges {
10211035 return ( isPropertySignature ( a ) || isPropertyDeclaration ( a ) ) && isClassOrTypeElement ( b ) && b . name ! . kind === SyntaxKind . ComputedPropertyName
10221036 || isStatementButNotDeclaration ( a ) && isStatementButNotDeclaration ( b ) ; // TODO: only if b would start with a `(` or `[`
10231037 }
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+ }
10241204}
0 commit comments