@@ -236,7 +236,7 @@ namespace ts.textChanges {
private readonly deletedNodes : { readonly sourceFile : SourceFile , readonly node : Node | NodeArray < TypeParameterDeclaration > } [ ] = [ ] ;
public static fromContext ( context : TextChangesContext ) : ChangeTracker {
return new ChangeTracker ( getNewLineOrDefaultFromHost ( context . host , context . formatContext . options ) , context . formatContext , context . preferences ) ;
return new ChangeTracker ( getNewLineOrDefaultFromHost ( context . host , context . formatContext . options ) , context . formatContext ) ;
}
public static with ( context : TextChangesContext , cb : ( tracker : ChangeTracker ) = > void ) : FileTextChanges [ ] {
@@ -246,7 +246,7 @@ namespace ts.textChanges {
}
/** Public for tests only. Other callers should use `ChangeTracker.with`. */
constructor ( private readonly newLineCharacter : string , private readonly formatContext : formatting.FormatContext , private preferences : UserPreferences ) { }
constructor ( private readonly newLineCharacter : string , private readonly formatContext : formatting.FormatContext ) { }
public deleteRange ( sourceFile : SourceFile , range : TextRange ) : void {
this . changes . push ( { kind : ChangeKind.Remove , sourceFile , range } ) ;
@@ -753,9 +753,9 @@ namespace ts.textChanges {
public getChanges ( validate? : ValidateNonFormattedText ) : FileTextChanges [ ] {
this . finishDeleteDeclarations ( ) ;
this . finishClassesWithNodesInsertedAtStart ( ) ;
const changes = changesToText . getTextChangesFromChanges ( this . changes , this . newLineCharacter , this . formatContext , this . preferences , validate ) ;
const changes = changesToText . getTextChangesFromChanges ( this . changes , this . newLineCharacter , this . formatContext , validate ) ;
for ( const { oldFile , fileName , statements } of this . newFiles ) {
changes . push ( changesToText . newFileChanges ( oldFile , fileName , statements , this . newLineCharacter , this . formatContext , this . preferences ) ) ;
changes . push ( changesToText . newFileChanges ( oldFile , fileName , statements , this . newLineCharacter , this . formatContext ) ) ;
}
return changes ;
}
@@ -779,12 +779,12 @@ namespace ts.textChanges {
export type ValidateNonFormattedText = ( node : Node , text : string ) = > void ;
export function getNewFileText ( statements : ReadonlyArray < Statement > , scriptKind : ScriptKind , newLineCharacter : string , formatContext : formatting.FormatContext , preferences : UserPreferences ) : string {
return changesToText . newFileChangesWorker ( /*oldFile*/ undefined , scriptKind , statements , newLineCharacter , formatContext , preferences ) ;
export function getNewFileText ( statements : ReadonlyArray < Statement > , scriptKind : ScriptKind , newLineCharacter : string , formatContext : formatting.FormatContext ) : string {
return changesToText . newFileChangesWorker ( /*oldFile*/ undefined , scriptKind , statements , newLineCharacter , formatContext ) ;
}
namespace changesToText {
export function getTextChangesFromChanges ( changes : ReadonlyArray < Change > , newLineCharacter : string , formatContext : formatting.FormatContext , preferences : UserPreferences , validate : ValidateNonFormattedText | undefined ) : FileTextChanges [ ] {
export function getTextChangesFromChanges ( changes : ReadonlyArray < Change > , newLineCharacter : string , formatContext : formatting.FormatContext , validate : ValidateNonFormattedText | undefined ) : FileTextChanges [ ] {
return group ( changes , c = > c . sourceFile . path ) . map ( changesInFile = > {
const sourceFile = changesInFile [ 0 ] . sourceFile ;
// order changes by start position
@@ -796,25 +796,25 @@ namespace ts.textChanges {
` ${ JSON . stringify ( normalized [ i ] . range ) } and ${ JSON . stringify ( normalized [ i + 1 ] . range ) } ` ) ;
}
const textChanges = normalized . map ( c = >
createTextChange ( createTextSpanFromRange ( c . range ) , computeNewText ( c , sourceFile , newLineCharacter , formatContext , preferences , validate ) ) ) ;
createTextChange ( createTextSpanFromRange ( c . range ) , computeNewText ( c , sourceFile , newLineCharacter , formatContext , validate ) ) ) ;
return { fileName : sourceFile.fileName , textChanges } ;
} ) ;
}
export function newFileChanges ( oldFile : SourceFile | undefined , fileName : string , statements : ReadonlyArray < Statement > , newLineCharacter : string , formatContext : formatting.FormatContext , preferences : UserPreferences ) : FileTextChanges {
const text = newFileChangesWorker ( oldFile , getScriptKindFromFileName ( fileName ) , statements , newLineCharacter , formatContext , preferences ) ;
export function newFileChanges ( oldFile : SourceFile | undefined , fileName : string , statements : ReadonlyArray < Statement > , newLineCharacter : string , formatContext : formatting.FormatContext ) : FileTextChanges {
const text = newFileChangesWorker ( oldFile , getScriptKindFromFileName ( fileName ) , statements , newLineCharacter , formatContext ) ;
return { fileName , textChanges : [ createTextChange ( createTextSpan ( 0 , 0 ) , text ) ] , isNewFile : true } ;
}
export function newFileChangesWorker ( oldFile : SourceFile | undefined , scriptKind : ScriptKind , statements : ReadonlyArray < Statement > , newLineCharacter : string , formatContext : formatting.FormatContext , preferences : UserPreferences ) : string {
export function newFileChangesWorker ( oldFile : SourceFile | undefined , scriptKind : ScriptKind , statements : ReadonlyArray < Statement > , newLineCharacter : string , formatContext : formatting.FormatContext ) : string {
// TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this
const nonFormattedText = statements . map ( s = > getNonformattedText ( s , oldFile , newLineCharacter , preferences ) . text ) . join ( newLineCharacter ) ;
const nonFormattedText = statements . map ( s = > getNonformattedText ( s , oldFile , newLineCharacter ) . text ) . join ( newLineCharacter ) ;
const sourceFile = createSourceFile ( "any file name" , nonFormattedText , ScriptTarget . ESNext , /*setParentNodes*/ true , scriptKind ) ;
const changes = formatting . formatDocument ( sourceFile , formatContext ) ;
return applyChanges ( nonFormattedText , changes ) + newLineCharacter ;
}
function computeNewText ( change : Change , sourceFile : SourceFile , newLineCharacter : string , formatContext : formatting.FormatContext , preferences : UserPreferences , validate : ValidateNonFormattedText | undefined ) : string {
function computeNewText ( change : Change , sourceFile : SourceFile , newLineCharacter : string , formatContext : formatting.FormatContext , validate : ValidateNonFormattedText | undefined ) : string {
if ( change . kind === ChangeKind . Remove ) {
return "" ;
}
@@ -823,7 +823,7 @@ namespace ts.textChanges {
}
const { options = { } , range : { pos } } = change ;
const format = ( n : Node ) = > getFormattedTextOfNode ( n , sourceFile , pos , options , newLineCharacter , formatContext , preferences , validate ) ;
const format = ( n : Node ) = > getFormattedTextOfNode ( n , sourceFile , pos , options , newLineCharacter , formatContext , validate ) ;
const text = change . kind === ChangeKind . ReplaceWithMultipleNodes
? change . nodes . map ( n = > removeSuffix ( format ( n ) , newLineCharacter ) ) . join ( change . options ! . joiner || newLineCharacter ) // TODO: GH#18217
: format ( change . node ) ;
@@ -832,30 +832,37 @@ namespace ts.textChanges {
return ( options . prefix || "" ) + noIndent + ( options . suffix || "" ) ;
}
function getFormatCodeSettingsForWriting ( context : formatting.FormatContext , sourceFile : SourceFile ) : FormatCodeSettings {
return {
. . . context . options ,
// If the user has no semicolon preference defined, try to match whatever’ s in the source file
semicolonPreference : ! context . options . semicolonPreference || context . options . semicolonPreference === SemicolonPreference . Ignore
? probablyUsesSemicolons ( sourceFile ) ? SemicolonPreference.Ignore : SemicolonPreference.Remove
: context . options . semicolonPreference ,
} ;
}
/** Note: this may mutate `nodeIn`. */
function getFormattedTextOfNode ( nodeIn : Node , sourceFile : SourceFile , pos : number , { indentation , prefix , delta } : InsertNodeOptions , newLineCharacter : string , formatContext : formatting.FormatContext , preferences : UserPreferences , validate : ValidateNonFormattedText | undefined ) : string {
const { node , text } = getNonformattedText ( nodeIn , sourceFile , newLineCharacter , preferences ) ;
function getFormattedTextOfNode ( nodeIn : Node , sourceFile : SourceFile , pos : number , { indentation , prefix , delta } : InsertNodeOptions , newLineCharacter : string , formatContext : formatting.FormatContext , validate : ValidateNonFormattedText | undefined ) : string {
const { node , text } = getNonformattedText ( nodeIn , sourceFile , newLineCharacter ) ;
if ( validate ) validate ( node , text ) ;
const { options : formatOptions } = formatContext ;
const formatOptions = getFormatCodeSettingsForWriting ( formatContext , sourceFile ) ;
const initialIndentation =
indentation !== undefined
? indentation
: formatting.SmartIndenter.getIndentation ( pos , sourceFile , formatOptions , prefix === newLineCharacter || getLineStartPositionForPosition ( pos , sourceFile ) === pos ) ;
if ( delta === undefined ) {
delta = formatting . SmartIndenter . shouldIndentChildNode ( formatContext . options , nodeIn ) ? ( formatOptions . indentSize || 0 ) : 0 ;
delta = formatting . SmartIndenter . shouldIndentChildNode ( formatOptions , nodeIn ) ? ( formatOptions . indentSize || 0 ) : 0 ;
}
const file : SourceFileLike = { text , getLineAndCharacterOfPosition ( pos ) { return getLineAndCharacterOfPosition ( this , pos ) ; } } ;
const changes = formatting . formatNodeGivenIndentation ( node , file , sourceFile . languageVariant , initialIndentation , delta , formatContext ) ;
const changes = formatting . formatNodeGivenIndentation ( node , file , sourceFile . languageVariant , initialIndentation , delta , { . . . formatContext , options : formatOptions } ) ;
return applyChanges ( text , changes ) ;
}
/** Note: output node may be mutated input node. */
export function getNonformattedText ( node : Node , sourceFile : SourceFile | undefined , newLineCharacter : string , { semicolonPreference } : UserPreferences ) : { text : string , node : Node } {
const omitTrailingSemicolon = semicolonPreference === "no semicolons" ? true :
semicolonPreference === "semicolons" ? false :
! ! sourceFile && ! probablyUsesSemicolons ( sourceFile ) ;
const writer = createWriter ( newLineCharacter , omitTrailingSemicolon ) ;
export function getNonformattedText ( node : Node , sourceFile : SourceFile | undefined , newLineCharacter : string ) : { text : string , node : Node } {
const writer = createWriter ( newLineCharacter ) ;
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed ;
createPrinter ( { newLine , neverAsciiEscape : true } , writer ) . writeNode ( EmitHint . Unspecified , node , sourceFile , writer ) ;
return { text : writer.getText ( ) , node : assignPositionsToNode ( node ) } ;
@@ -897,11 +904,11 @@ namespace ts.textChanges {
interface TextChangesWriter extends EmitTextWriter , PrintHandlers { }
function createWriter ( newLine : string , omitTrailingSemicolon? : boolean ) : TextChangesWriter {
function createWriter ( newLine : string ) : TextChangesWriter {
let lastNonTriviaPosition = 0 ;
const writer = omitTrailingSemicolon ? getTrailingSemicolonOmittingWriter ( createTextWriter ( newLine ) ) : createTextWriter ( newLine ) ;
const writer = createTextWriter ( newLine ) ;
const onEmitNode : PrintHandlers [ "onEmitNode" ] = ( hint , node , printCallback ) = > {
if ( node ) {
setPos ( node , lastNonTriviaPosition ) ;