@@ -212,11 +212,7 @@ namespace ts.textChanges {
212
212
}
213
213
214
214
/** Public for tests only. Other callers should use `ChangeTracker.with`. */
215
- constructor (
216
- private readonly newLineCharacter : string ,
217
- private readonly formatContext : ts . formatting . FormatContext ,
218
- private readonly validator ?: ( text : NonFormattedText ) => void ) {
219
- }
215
+ constructor ( private readonly newLineCharacter : string , private readonly formatContext : ts . formatting . FormatContext ) { }
220
216
221
217
public deleteRange ( sourceFile : SourceFile , range : TextRange ) {
222
218
this . changes . push ( { kind : ChangeKind . Remove , sourceFile, range } ) ;
@@ -590,104 +586,83 @@ namespace ts.textChanges {
590
586
} ) ;
591
587
}
592
588
593
- public getChanges ( ) : FileTextChanges [ ] {
589
+ /**
590
+ * Note: after calling this, the TextChanges object must be discarded!
591
+ * @param validate only for tests
592
+ * The reason we must validate as part of this method is that `getNonFormattedText` changes the node's positions,
593
+ * so we can only call this once and can't get the non-formatted text separately.
594
+ */
595
+ public getChanges ( validate ?: ValidateNonFormattedText ) : FileTextChanges [ ] {
594
596
this . finishInsertNodeAtClassStart ( ) ;
595
- return group ( this . changes , c => c . sourceFile . path ) . map ( changesInFile => {
597
+ return changesToText . getTextChangesFromChanges ( this . changes , this . newLineCharacter , this . formatContext , validate ) ;
598
+ }
599
+ }
600
+
601
+ export type ValidateNonFormattedText = ( node : Node , text : string ) => void ;
602
+
603
+ namespace changesToText {
604
+ export function getTextChangesFromChanges ( changes : ReadonlyArray < Change > , newLineCharacter : string , formatContext : formatting . FormatContext , validate : ValidateNonFormattedText | undefined ) : FileTextChanges [ ] {
605
+ return group ( changes , c => c . sourceFile . path ) . map ( changesInFile => {
596
606
const sourceFile = changesInFile [ 0 ] . sourceFile ;
597
- const textChanges = ChangeTracker . normalize ( changesInFile ) . map ( c =>
598
- createTextChange ( createTextSpanFromRange ( c . range ) , this . computeNewText ( c , sourceFile ) ) ) ;
607
+ // order changes by start position
608
+ const normalized = stableSort ( changesInFile , ( a , b ) => a . range . pos - b . range . pos ) ;
609
+ // verify that change intervals do not overlap, except possibly at end points.
610
+ for ( let i = 0 ; i < normalized . length - 2 ; i ++ ) {
611
+ Debug . assert ( normalized [ i ] . range . end <= normalized [ i + 1 ] . range . pos , "Changes overlap" , ( ) =>
612
+ `${ JSON . stringify ( normalized [ i ] . range ) } and ${ JSON . stringify ( normalized [ i + 1 ] . range ) } ` ) ;
613
+ }
614
+ const textChanges = normalized . map ( c =>
615
+ createTextChange ( createTextSpanFromRange ( c . range ) , computeNewText ( c , sourceFile , newLineCharacter , formatContext , validate ) ) ) ;
599
616
return { fileName : sourceFile . fileName , textChanges } ;
600
617
} ) ;
601
618
}
602
619
603
- private computeNewText ( change : Change , sourceFile : SourceFile ) : string {
620
+ function computeNewText ( change : Change , sourceFile : SourceFile , newLineCharacter : string , formatContext : formatting . FormatContext , validate : ValidateNonFormattedText | undefined ) : string {
604
621
if ( change . kind === ChangeKind . Remove ) {
605
- // deletion case
606
622
return "" ;
607
623
}
608
624
609
- const options = change . options || { } ;
610
- let text : string ;
611
- const pos = change . range . pos ;
612
- const posStartsLine = getLineStartPositionForPosition ( pos , sourceFile ) === pos ;
613
- if ( change . kind === ChangeKind . ReplaceWithMultipleNodes ) {
614
- const lastIndex = change . nodes . length - 1 ;
615
- const parts = change . nodes . map ( ( n , index ) => {
616
- const formatted = this . getFormattedTextOfNode ( n , sourceFile , pos , options ) ;
617
- return index === lastIndex || endsWith ( formatted , this . newLineCharacter )
618
- ? formatted
619
- : ( formatted + this . newLineCharacter ) ;
620
- } ) ;
621
- text = parts . join ( "" ) ;
622
- }
623
- else {
624
- Debug . assert ( change . kind === ChangeKind . ReplaceWithSingleNode , "change.kind === ReplaceWithSingleNode" ) ;
625
- text = this . getFormattedTextOfNode ( change . node , sourceFile , pos , options ) ;
626
- }
625
+ const { options = { } , range : { pos } } = change ;
626
+ const format = ( n : Node ) => getFormattedTextOfNode ( n , sourceFile , pos , options , newLineCharacter , formatContext , validate ) ;
627
+ const text = change . kind === ChangeKind . ReplaceWithMultipleNodes
628
+ ? change . nodes . map ( n => removeSuffix ( format ( n ) , newLineCharacter ) ) . join ( newLineCharacter )
629
+ : format ( change . node ) ;
627
630
// strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line
628
- text = ( posStartsLine || options . indentation !== undefined ) ? text : text . replace ( / ^ \s + / , "" ) ;
629
- return ( options . prefix || "" ) + text + ( options . suffix || "" ) ;
631
+ const noIndent = ( options . indentation !== undefined || getLineStartPositionForPosition ( pos , sourceFile ) === pos ) ? text : text . replace ( / ^ \s + / , "" ) ;
632
+ return ( options . prefix || "" ) + noIndent + ( options . suffix || "" ) ;
630
633
}
631
634
632
- private getFormattedTextOfNode ( node : Node , sourceFile : SourceFile , pos : number , options : ChangeNodeOptions ) : string {
633
- const nonformattedText = getNonformattedText ( node , sourceFile , this . newLineCharacter ) ;
634
- if ( this . validator ) {
635
- this . validator ( nonformattedText ) ;
636
- }
637
-
638
- const { options : formatOptions } = this . formatContext ;
639
- const posStartsLine = getLineStartPositionForPosition ( pos , sourceFile ) === pos ;
640
-
635
+ /** Note: this may mutate `nodeIn`. */
636
+ function getFormattedTextOfNode ( nodeIn : Node , sourceFile : SourceFile , pos : number , options : ChangeNodeOptions , newLineCharacter : string , formatContext : formatting . FormatContext , validate : ValidateNonFormattedText | undefined ) : string {
637
+ const { node, text } = getNonformattedText ( nodeIn , sourceFile , newLineCharacter ) ;
638
+ if ( validate ) validate ( node , text ) ;
639
+ const { options : formatOptions } = formatContext ;
641
640
const initialIndentation =
642
641
options . indentation !== undefined
643
642
? options . indentation
644
643
: ( options . useIndentationFromFile !== false )
645
- ? formatting . SmartIndenter . getIndentation ( pos , sourceFile , formatOptions , posStartsLine || ( options . prefix === this . newLineCharacter ) )
644
+ ? formatting . SmartIndenter . getIndentation ( pos , sourceFile , formatOptions , options . prefix === newLineCharacter || getLineStartPositionForPosition ( pos , sourceFile ) === pos )
646
645
: 0 ;
647
646
const delta =
648
647
options . delta !== undefined
649
648
? options . delta
650
- : formatting . SmartIndenter . shouldIndentChildNode ( node )
649
+ : formatting . SmartIndenter . shouldIndentChildNode ( nodeIn )
651
650
? ( formatOptions . indentSize || 0 )
652
651
: 0 ;
653
-
654
- return applyFormatting ( nonformattedText , sourceFile , initialIndentation , delta , this . formatContext ) ;
652
+ const file : SourceFileLike = { text, getLineAndCharacterOfPosition ( pos ) { return getLineAndCharacterOfPosition ( this , pos ) ; } } ;
653
+ const changes = formatting . formatNodeGivenIndentation ( node , file , sourceFile . languageVariant , initialIndentation , delta , formatContext ) ;
654
+ return applyChanges ( text , changes ) ;
655
655
}
656
656
657
- private static normalize ( changes : ReadonlyArray < Change > ) : ReadonlyArray < Change > {
658
- // order changes by start position
659
- const normalized = stableSort ( changes , ( a , b ) => a . range . pos - b . range . pos ) ;
660
- // verify that change intervals do not overlap, except possibly at end points.
661
- for ( let i = 0 ; i < normalized . length - 2 ; i ++ ) {
662
- Debug . assert ( normalized [ i ] . range . end <= normalized [ i + 1 ] . range . pos ) ;
663
- }
664
- return normalized ;
657
+ /** Note: output node may be mutated input node. */
658
+ function getNonformattedText ( node : Node , sourceFile : SourceFile | undefined , newLineCharacter : string ) : { text : string , node : Node } {
659
+ const writer = new Writer ( newLineCharacter ) ;
660
+ const newLine = newLineCharacter === "\n" ? NewLineKind . LineFeed : NewLineKind . CarriageReturnLineFeed ;
661
+ createPrinter ( { newLine } , writer ) . writeNode ( EmitHint . Unspecified , node , sourceFile , writer ) ;
662
+ return { text : writer . getText ( ) , node : assignPositionsToNode ( node ) } ;
665
663
}
666
664
}
667
665
668
- export interface NonFormattedText {
669
- readonly text : string ;
670
- readonly node : Node ;
671
- }
672
-
673
- function getNonformattedText ( node : Node , sourceFile : SourceFile | undefined , newLine : string ) : NonFormattedText {
674
- const writer = new Writer ( newLine ) ;
675
- const printer = createPrinter ( { newLine : newLine === "\n" ? NewLineKind . LineFeed : NewLineKind . CarriageReturnLineFeed } , writer ) ;
676
- printer . writeNode ( EmitHint . Unspecified , node , sourceFile , writer ) ;
677
- return { text : writer . getText ( ) , node : assignPositionsToNode ( node ) } ;
678
- }
679
-
680
- function applyFormatting ( nonFormattedText : NonFormattedText , sourceFile : SourceFile , initialIndentation : number , delta : number , formatContext : ts . formatting . FormatContext ) {
681
- const lineMap = computeLineStarts ( nonFormattedText . text ) ;
682
- const file : SourceFileLike = {
683
- text : nonFormattedText . text ,
684
- lineMap,
685
- getLineAndCharacterOfPosition : pos => computeLineAndCharacterOfPosition ( lineMap , pos )
686
- } ;
687
- const changes = formatting . formatNodeGivenIndentation ( nonFormattedText . node , file , sourceFile . languageVariant , initialIndentation , delta , formatContext ) ;
688
- return applyChanges ( nonFormattedText . text , changes ) ;
689
- }
690
-
691
666
export function applyChanges ( text : string , changes : TextChange [ ] ) : string {
692
667
for ( let i = changes . length - 1 ; i >= 0 ; i -- ) {
693
668
const change = changes [ i ] ;
0 commit comments