@@ -212,8 +212,7 @@ namespace ts.textChanges {
212212 export class ChangeTracker {
213213 private readonly changes : Change [ ] = [ ] ;
214214 private readonly deletedNodesInLists : true [ ] = [ ] ; // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`.
215- // Map from class id to nodes to insert at the start
216- private readonly nodesInsertedAtClassStarts = createMap < { sourceFile : SourceFile , cls : ClassLikeDeclaration , members : ClassElement [ ] } > ( ) ;
215+ private readonly classesWithNodesInsertedAtStart = createMap < ClassDeclaration > ( ) ; // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
217216
218217 public static fromContext ( context : TextChangesContext ) : ChangeTracker {
219218 return new ChangeTracker ( getNewLineOrDefaultFromHost ( context . host , context . formatContext . options ) , context . formatContext ) ;
@@ -343,8 +342,7 @@ namespace ts.textChanges {
343342 }
344343
345344 public insertNodeBefore ( sourceFile : SourceFile , before : Node , newNode : Node , blankLineBetween = false ) {
346- const pos = getAdjustedStartPosition ( sourceFile , before , { } , Position . Start ) ;
347- return this . replaceRange ( sourceFile , { pos, end : pos } , newNode , this . getOptionsForInsertNodeBefore ( before , blankLineBetween ) ) ;
345+ this . insertNodeAt ( sourceFile , getAdjustedStartPosition ( sourceFile , before , { } , Position . Start ) , newNode , this . getOptionsForInsertNodeBefore ( before , blankLineBetween ) ) ;
348346 }
349347
350348 public insertModifierBefore ( sourceFile : SourceFile , modifier : SyntaxKind , before : Node ) : void {
@@ -443,21 +441,20 @@ namespace ts.textChanges {
443441 }
444442
445443 public insertNodeAtClassStart ( sourceFile : SourceFile , cls : ClassLikeDeclaration , newElement : ClassElement ) : void {
446- const firstMember = firstOrUndefined ( cls . members ) ;
447- if ( ! firstMember ) {
448- const id = getNodeId ( cls ) . toString ( ) ;
449- const newMembers = this . nodesInsertedAtClassStarts . get ( id ) ;
450- if ( newMembers ) {
451- Debug . assert ( newMembers . sourceFile === sourceFile && newMembers . cls === cls ) ;
452- newMembers . members . push ( newElement ) ;
444+ const clsStart = cls . getStart ( sourceFile ) ;
445+ let prefix = "" ;
446+ let suffix = this . newLineCharacter ;
447+ if ( addToSeen ( this . classesWithNodesInsertedAtStart , getNodeId ( cls ) , cls ) ) {
448+ prefix = this . newLineCharacter ;
449+ // For `class C {\n}`, don't add the trailing "\n"
450+ if ( cls . members . length === 0 && ! ( positionsAreOnSameLine as any ) ( ...getClassBraceEnds ( cls , sourceFile ) , sourceFile ) ) { // TODO: GH#4130 remove 'as any'
451+ suffix = "" ;
453452 }
454- else {
455- this . nodesInsertedAtClassStarts . set ( id , { sourceFile, cls, members : [ newElement ] } ) ;
456- }
457- }
458- else {
459- this . insertNodeBefore ( sourceFile , firstMember , newElement ) ;
460453 }
454+
455+ const indentation = formatting . SmartIndenter . findFirstNonWhitespaceColumn ( getLineStartPositionForPosition ( clsStart , sourceFile ) , clsStart , sourceFile , this . formatContext . options )
456+ + this . formatContext . options . indentSize ;
457+ this . insertNodeAt ( sourceFile , cls . members . pos , newElement , { indentation, prefix, suffix } ) ;
461458 }
462459
463460 public insertNodeAfter ( sourceFile : SourceFile , after : Node , newNode : Node ) : this {
@@ -638,12 +635,14 @@ namespace ts.textChanges {
638635 return this ;
639636 }
640637
641- private finishInsertNodeAtClassStart ( ) : void {
642- this . nodesInsertedAtClassStarts . forEach ( ( { sourceFile, cls, members } ) => {
643- const newCls = cls . kind === SyntaxKind . ClassDeclaration
644- ? updateClassDeclaration ( cls , cls . decorators , cls . modifiers , cls . name , cls . typeParameters , cls . heritageClauses , members )
645- : updateClassExpression ( cls , cls . modifiers , cls . name , cls . typeParameters , cls . heritageClauses , members ) ;
646- this . replaceNode ( sourceFile , cls , newCls ) ;
638+ private finishClassesWithNodesInsertedAtStart ( ) : void {
639+ this . classesWithNodesInsertedAtStart . forEach ( cls => {
640+ const sourceFile = cls . getSourceFile ( ) ;
641+ const [ openBraceEnd , closeBraceEnd ] = getClassBraceEnds ( cls , sourceFile ) ;
642+ // For `class C { }` remove the whitespace inside the braces.
643+ if ( positionsAreOnSameLine ( openBraceEnd , closeBraceEnd , sourceFile ) && openBraceEnd !== closeBraceEnd - 1 ) {
644+ this . deleteRange ( sourceFile , createTextRange ( openBraceEnd , closeBraceEnd - 1 ) ) ;
645+ }
647646 } ) ;
648647 }
649648
@@ -654,11 +653,15 @@ namespace ts.textChanges {
654653 * so we can only call this once and can't get the non-formatted text separately.
655654 */
656655 public getChanges ( validate ?: ValidateNonFormattedText ) : FileTextChanges [ ] {
657- this . finishInsertNodeAtClassStart ( ) ;
656+ this . finishClassesWithNodesInsertedAtStart ( ) ;
658657 return changesToText . getTextChangesFromChanges ( this . changes , this . newLineCharacter , this . formatContext , validate ) ;
659658 }
660659 }
661660
661+ function getClassBraceEnds ( cls : ClassLikeDeclaration , sourceFile : SourceFile ) : [ number , number ] {
662+ return [ findChildOfKind ( cls , SyntaxKind . OpenBraceToken , sourceFile ) . end , findChildOfKind ( cls , SyntaxKind . CloseBraceToken , sourceFile ) . end ] ;
663+ }
664+
662665 export type ValidateNonFormattedText = ( node : Node , text : string ) => void ;
663666
664667 namespace changesToText {
0 commit comments