namespace ts . textChanges { /**
     * Currently for simplicity we store recovered positions on the node itself.
     * It can be changed to side-table later if we decide that current design is too invasive.
     */ function getPos ( n ) { const result = ( n as any ) . __pos ; Debug . assert ( typeof result === "number" ) ; return result ; } function setPos ( n , pos ) { Debug . assert ( typeof pos === "number" ) ; ( n as any ) . __pos = pos ; } function getEnd ( n ) { const result = ( n as any ) . __end ; Debug . assert ( typeof result === "number" ) ; return result ; } function setEnd ( n , end ) { Debug . assert ( typeof end === "number" ) ; ( n as any ) . __end = end ; } export interface ConfigurableStart { leadingTriviaOption ? : LeadingTriviaOption ; } export interface ConfigurableEnd { trailingTriviaOption ? : TrailingTriviaOption ; } export enum LeadingTriviaOption { /** Exclude all leading trivia (use getStart()) */ Exclude , /** Include leading trivia and,
         * if there are no line breaks between the node and the previous token,
         * include all trivia between the node and the previous token
         */ IncludeAll , /**
         * Include attached JSDoc comments
         */ JSDoc , /**
         * Only delete trivia on the same line as getStart().
         * Used to avoid deleting leading comments
         */ StartLine } export enum TrailingTriviaOption { /** Exclude all trailing trivia (use getEnd()) */ Exclude , /** Doesn't include whitespace, but does strip comments */ ExcludeWhitespace , /** Include trailing trivia */ Include } function skipWhitespacesAndLineBreaks ( text , start ) { return skipTrivia ( text , start , false , true ) ; } function hasCommentsBeforeLineBreak ( text , start ) { let i = start ; while ( i < text . length ) { const ch = text . charCodeAt ( i ) ; if ( isWhiteSpaceSingleLine ( ch ) ) { i ++ ; continue ; } return ch === CharacterCodes . slash ; } return false ; } /**
     * Usually node.pos points to a position immediately after the previous token.
     * If this position is used as a beginning of the span to remove - it might lead to removing the trailing trivia of the previous node, i.e:
     * const x; // this is x
     *        ^ - pos for the next variable declaration will point here
     * const y; // this is y
     *        ^ - end for previous variable declaration
     * Usually leading trivia of the variable declaration 'y' should not include trailing trivia (whitespace, comment 'this is x' and newline) from the preceding
     * variable declaration and trailing trivia for 'y' should include (whitespace, comment 'this is y', newline).
     * By default when removing nodes we adjust start and end positions to respect specification of the trivia above.
     * If pos\end should be interpreted literally (that is, withouth including leading and trailing trivia), `leadingTriviaOption` should be set to `LeadingTriviaOption.Exclude`
     * and `trailingTriviaOption` to `TrailingTriviaOption.Exclude`.
     */ export interface ConfigurableStartEnd extends ConfigurableStart , ConfigurableEnd { } const useNonAdjustedPositions = { leadingTriviaOption : LeadingTriviaOption . Exclude , trailingTriviaOption : TrailingTriviaOption . Exclude , } ; export interface InsertNodeOptions { /**
         * Text to be inserted before the new node
         */ prefix ? : string ; /**
         * Text to be inserted after the new node
         */ suffix ? : string ; /**
         * Text of inserted node will be formatted with this indentation, otherwise indentation will be inferred from the old node
         */ indentation ? : number ; /**
         * Text of inserted node will be formatted with this delta, otherwise delta will be inferred from the new node kind
         */ delta ? : number ; /**
         * Do not trim leading white spaces in the edit range
         */ preserveLeadingWhitespace ? : boolean ; } export interface ReplaceWithMultipleNodesOptions extends InsertNodeOptions { readonly joiner ? : string ; } enum ChangeKind { Remove , ReplaceWithSingleNode , ReplaceWithMultipleNodes , Text } type Change = ReplaceWithSingleNode | ReplaceWithMultipleNodes | RemoveNode | ChangeText ; interface BaseChange { readonly sourceFile : SourceFile ; readonly range : TextRange ; } export interface ChangeNodeOptions extends ConfigurableStartEnd , InsertNodeOptions { } interface ReplaceWithSingleNode extends BaseChange { readonly kind : ChangeKind . ReplaceWithSingleNode ; readonly node : Node ; readonly options ? : InsertNodeOptions ; } interface RemoveNode extends BaseChange { readonly kind : ChangeKind . Remove ; readonly node ? : never ; readonly options ? : never ; } interface ReplaceWithMultipleNodes extends BaseChange { readonly kind : ChangeKind . ReplaceWithMultipleNodes ; readonly nodes : readonly Node [ ] ; readonly options ? : ReplaceWithMultipleNodesOptions ; } interface ChangeText extends BaseChange { readonly kind : ChangeKind . Text ; readonly text : string ; } function getAdjustedRange ( sourceFile , startNode , endNode , options ) { return { pos : getAdjustedStartPosition ( sourceFile , startNode , options ) , end : getAdjustedEndPosition ( sourceFile , endNode , options ) } ; } function getAdjustedStartPosition ( sourceFile , node , options , hasTrailingComment = false ) { const { leadingTriviaOption } = options ; if ( leadingTriviaOption === LeadingTriviaOption . Exclude ) { return node . getStart ( sourceFile ) ; } if ( leadingTriviaOption === LeadingTriviaOption . StartLine ) { const startPos = node . getStart ( sourceFile ) ; const pos = getLineStartPositionForPosition ( startPos , sourceFile ) ; return rangeContainsPosition ( node , pos ) ? pos : startPos ; } if ( leadingTriviaOption === LeadingTriviaOption . JSDoc ) { const JSDocComments = getJSDocCommentRanges ( node , sourceFile . text ) ; if ( JSDocComments ?. length ) { return getLineStartPositionForPosition ( JSDocComments [ 0 ] . pos , sourceFile ) ; } } const fullStart = node . getFullStart ( ) ; const start = node . getStart ( sourceFile ) ; if ( fullStart === start ) { return start ; } const fullStartLine = getLineStartPositionForPosition ( fullStart , sourceFile ) ; const startLine = getLineStartPositionForPosition ( start , sourceFile ) ; if ( startLine === fullStartLine ) { return leadingTriviaOption === LeadingTriviaOption . IncludeAll ? fullStart : start ; } if ( hasTrailingComment ) { const comment = getLeadingCommentRanges ( sourceFile . text , fullStart ) ?. [ 0 ] || getTrailingCommentRanges ( sourceFile . text , fullStart ) ?. [ 0 ] ; if ( comment ) { return skipTrivia ( sourceFile . text , comment . end , true , true ) ; } } const nextLineStart = fullStart > 0 ? 1 : 0 ; let adjustedStartPosition = getStartPositionOfLine ( getLineOfLocalPosition ( sourceFile , fullStartLine ) + nextLineStart , sourceFile ) ; adjustedStartPosition = skipWhitespacesAndLineBreaks ( sourceFile . text , adjustedStartPosition ) ; return getStartPositionOfLine ( getLineOfLocalPosition ( sourceFile , adjustedStartPosition ) , sourceFile ) ; } /** Return the end position of a multiline comment of it is on another line; otherwise returns `undefined`; */ function getEndPositionOfMultilineTrailingComment ( sourceFile , node , options ) { const { end } = node ; const { trailingTriviaOption } = options ; if ( trailingTriviaOption === TrailingTriviaOption . Include ) { const comments = getTrailingCommentRanges ( sourceFile . text , end ) ; if ( comments ) { const nodeEndLine = getLineOfLocalPosition ( sourceFile , node . end ) ; for ( const comment of comments ) { if ( comment . kind === SyntaxKind . SingleLineCommentTrivia || getLineOfLocalPosition ( sourceFile , comment . pos ) > nodeEndLine ) { break ; } const commentEndLine = getLineOfLocalPosition ( sourceFile , comment . end ) ; if ( commentEndLine > nodeEndLine ) { return skipTrivia ( sourceFile . text , comment . end , true , true ) ; } } } } return undefined ; } function getAdjustedEndPosition ( sourceFile , node , options ) { const { end } = node ; const { trailingTriviaOption } = options ; if ( trailingTriviaOption === TrailingTriviaOption . Exclude ) { return end ; } if ( trailingTriviaOption === TrailingTriviaOption . ExcludeWhitespace ) { const comments = concatenate ( getTrailingCommentRanges ( sourceFile . text , end ) , getLeadingCommentRanges ( sourceFile . text , end ) ) ; const realEnd = comments ?. [ comments . length - 1 ] ?. end ; if ( realEnd ) { return realEnd ; } return end ; } const multilineEndPosition = getEndPositionOfMultilineTrailingComment ( sourceFile , node , options ) ; if ( multilineEndPosition ) { return multilineEndPosition ; } const newEnd = skipTrivia ( sourceFile . text , end , true ) ; return newEnd !== end && ( trailingTriviaOption === TrailingTriviaOption . Include || isLineBreak ( sourceFile . text . charCodeAt ( newEnd - 1 ) ) ) ? newEnd : end ; } /**
     * Checks if 'candidate' argument is a legal separator in the list that contains 'node' as an element
     */ function isSeparator ( node , candidate ) { return ! ! candidate && ! ! node . parent && ( candidate . kind === SyntaxKind . CommaToken || ( candidate . kind === SyntaxKind . SemicolonToken && node . parent . kind === SyntaxKind . ObjectLiteralExpression ) ) ; } export interface TextChangesContext { host : LanguageServiceHost ; formatContext : formatting . FormatContext ; preferences : UserPreferences ; } export type TypeAnnotatable = SignatureDeclaration | VariableDeclaration | ParameterDeclaration | PropertyDeclaration | PropertySignature ; export type ThisTypeAnnotatable = FunctionDeclaration | FunctionExpression ; export function isThisTypeAnnotatable ( containingFunction ) { return isFunctionExpression ( containingFunction ) || isFunctionDeclaration ( containingFunction ) ; } export class ChangeTracker { private readonly changes : Change [ ] = [ ] ; private readonly newFiles : { readonly oldFile : SourceFile | undefined ; readonly fileName : string ; readonly statements : readonly ( Statement | SyntaxKind . NewLineTrivia ) [ ] ; } [ ] = [ ] ; private readonly classesWithNodesInsertedAtStart = new Map < number , { readonly node : ClassDeclaration | InterfaceDeclaration | ObjectLiteralExpression ; readonly sourceFile : SourceFile ; } > ( ) ; private readonly deletedNodes : { readonly sourceFile : SourceFile ; readonly node : Node | NodeArray < TypeParameterDeclaration > ; } [ ] = [ ] ; public static fromContext ( context ) { return new ChangeTracker ( getNewLineOrDefaultFromHost ( context . host , context . formatContext . options ) , context . formatContext ) ; } public static with ( context , cb ) { const tracker = ChangeTracker . fromContext ( context ) ; cb ( tracker ) ; return tracker . getChanges ( ) ; } /** Public for tests only. Other callers should use `ChangeTracker.with`. */ constructor ( private readonly newLineCharacter , private readonly formatContext ) { } public pushRaw ( sourceFile , change ) { Debug . assertEqual ( sourceFile . fileName , change . fileName ) ; for ( const c of change . textChanges ) { this . changes . push ( { kind : ChangeKind . Text , sourceFile , text : c . newText , range : createTextRangeFromSpan ( c . span ) , } ) ; } } public deleteRange ( sourceFile , range ) { this . changes . push ( { kind : ChangeKind . Remove , sourceFile , range } ) ; } delete ( sourceFile , node ) { this . deletedNodes . push ( { sourceFile , node } ) ; } public deleteNode ( sourceFile , node , options = { leadingTriviaOption : LeadingTriviaOption . IncludeAll } ) { this . deleteRange ( sourceFile , getAdjustedRange ( sourceFile , node , node , options ) ) ; } public deleteNodes ( sourceFile , nodes , options = { leadingTriviaOption : LeadingTriviaOption . IncludeAll } , hasTrailingComment ) { for ( const node of nodes ) { const pos = getAdjustedStartPosition ( sourceFile , node , options , hasTrailingComment ) ; const end = getAdjustedEndPosition ( sourceFile , node , options ) ; this . deleteRange ( sourceFile , { pos , end } ) ; hasTrailingComment = ! ! getEndPositionOfMultilineTrailingComment ( sourceFile , node , options ) ; } } public deleteModifier ( sourceFile , modifier ) { this . deleteRange ( sourceFile , { pos : modifier . getStart ( sourceFile ) , end : skipTrivia ( sourceFile . text , modifier . end , true ) } ) ; } public deleteNodeRange ( sourceFile , startNode , endNode , options = { leadingTriviaOption : LeadingTriviaOption . IncludeAll } ) { const startPosition = getAdjustedStartPosition ( sourceFile , startNode , options ) ; const endPosition = getAdjustedEndPosition ( sourceFile , endNode , options ) ; this . deleteRange ( sourceFile , { pos : startPosition , end : endPosition } ) ; } public deleteNodeRangeExcludingEnd ( sourceFile , startNode , afterEndNode , options = { leadingTriviaOption : LeadingTriviaOption . IncludeAll } ) { const startPosition = getAdjustedStartPosition ( sourceFile , startNode , options ) ; const endPosition = afterEndNode === undefined ? sourceFile . text . length : getAdjustedStartPosition ( sourceFile , afterEndNode , options ) ; this . deleteRange ( sourceFile , { pos : startPosition , end : endPosition } ) ; } public replaceRange ( sourceFile , range , newNode , options = { } ) { this . changes . push ( { kind : ChangeKind . ReplaceWithSingleNode , sourceFile , range , options , node : newNode } ) ; } public replaceNode ( sourceFile , oldNode , newNode , options = useNonAdjustedPositions ) { this . replaceRange ( sourceFile , getAdjustedRange ( sourceFile , oldNode , oldNode , options ) , newNode , options ) ; } public replaceNodeRange ( sourceFile , startNode , endNode , newNode , options = useNonAdjustedPositions ) { this . replaceRange ( sourceFile , getAdjustedRange ( sourceFile , startNode , endNode , options ) , newNode , options ) ; } private replaceRangeWithNodes ( sourceFile , range , newNodes , options = { } ) { this . changes . push ( { kind : ChangeKind . ReplaceWithMultipleNodes , sourceFile , range , options , nodes : newNodes } ) ; } public replaceNodeWithNodes ( sourceFile , oldNode , newNodes , options = useNonAdjustedPositions ) { this . replaceRangeWithNodes ( sourceFile , getAdjustedRange ( sourceFile , oldNode , oldNode , options ) , newNodes , options ) ; } public replaceNodeWithText ( sourceFile , oldNode , text ) { this . replaceRangeWithText ( sourceFile , getAdjustedRange ( sourceFile , oldNode , oldNode , useNonAdjustedPositions ) , text ) ; } public replaceNodeRangeWithNodes ( sourceFile , startNode , endNode , newNodes , options = useNonAdjustedPositions ) { this . replaceRangeWithNodes ( sourceFile , getAdjustedRange ( sourceFile , startNode , endNode , options ) , newNodes , options ) ; } public nodeHasTrailingComment ( sourceFile , oldNode , configurableEnd = useNonAdjustedPositions ) { return ! ! getEndPositionOfMultilineTrailingComment ( sourceFile , oldNode , configurableEnd ) ; } private nextCommaToken ( sourceFile , node ) { const next = findNextToken ( node , node . parent , sourceFile ) ; return next && next . kind === SyntaxKind . CommaToken ? next : undefined ; } public replacePropertyAssignment ( sourceFile , oldNode , newNode ) { const suffix = this . nextCommaToken ( sourceFile , oldNode ) ? "" : ( "," + this . newLineCharacter ) ; this . replaceNode ( sourceFile , oldNode , newNode , { suffix } ) ; } public insertNodeAt ( sourceFile , pos , newNode , options = { } ) { this . replaceRange ( sourceFile , createRange ( pos ) , newNode , options ) ; } private insertNodesAt ( sourceFile , pos , newNodes , options = { } ) { this . replaceRangeWithNodes ( sourceFile , createRange ( pos ) , newNodes , options ) ; } public insertNodeAtTopOfFile ( sourceFile , newNode , blankLineBetween ) { this . insertAtTopOfFile ( sourceFile , newNode , blankLineBetween ) ; } public insertNodesAtTopOfFile ( sourceFile , newNodes , blankLineBetween ) { this . insertAtTopOfFile ( sourceFile , newNodes , blankLineBetween ) ; } private insertAtTopOfFile ( sourceFile , insert , blankLineBetween ) { const pos = getInsertionPositionAtSourceFileTop ( sourceFile ) ; const options = { prefix : pos === 0 ? undefined : this . newLineCharacter , suffix : ( isLineBreak ( sourceFile . text . charCodeAt ( pos ) ) ? "" : this . newLineCharacter ) + ( blankLineBetween ? this . newLineCharacter : "" ) , } ; if ( isArray ( insert ) ) { this . insertNodesAt ( sourceFile , pos , insert , options ) ; } else { this . insertNodeAt ( sourceFile , pos , insert , options ) ; } } public insertFirstParameter ( sourceFile , parameters , newParam ) { const p0 = firstOrUndefined ( parameters ) ; if ( p0 ) { this . insertNodeBefore ( sourceFile , p0 , newParam ) ; } else { this . insertNodeAt ( sourceFile , parameters . pos , newParam ) ; } } public insertNodeBefore ( sourceFile , before , newNode , blankLineBetween = false , options = { } ) { this . insertNodeAt ( sourceFile , getAdjustedStartPosition ( sourceFile , before , options ) , newNode , this . getOptionsForInsertNodeBefore ( before , newNode , blankLineBetween ) ) ; } public insertModifierAt ( sourceFile , pos , modifier , options = { } ) { this . insertNodeAt ( sourceFile , pos , factory . createToken ( modifier ) , options ) ; } public insertModifierBefore ( sourceFile , modifier , before ) { return this . insertModifierAt ( sourceFile , before . getStart ( sourceFile ) , modifier , { suffix : " " } ) ; } public insertCommentBeforeLine ( sourceFile , lineNumber , position , commentText ) { const lineStartPosition = getStartPositionOfLine ( lineNumber , sourceFile ) ; const startPosition = getFirstNonSpaceCharacterPosition ( sourceFile . text , lineStartPosition ) ; const insertAtLineStart = isValidLocationToAddComment ( sourceFile , startPosition ) ; const token = getTouchingToken ( sourceFile , insertAtLineStart ? startPosition : position ) ; const indent = sourceFile . text . slice ( lineStartPosition , startPosition ) ; const text = `${ insertAtLineStart ? "" : this . newLineCharacter }//${ commentText }${ this . newLineCharacter }${ indent }` ; this . insertText ( sourceFile , token . getStart ( sourceFile ) , text ) ; } public insertJsdocCommentBefore ( sourceFile , node , tag ) { const fnStart = node . getStart ( sourceFile ) ; if ( node . jsDoc ) { for ( const jsdoc of node . jsDoc ) { this . deleteRange ( sourceFile , { pos : getLineStartPositionForPosition ( jsdoc . getStart ( sourceFile ) , sourceFile ) , end : getAdjustedEndPosition ( sourceFile , jsdoc , { } ) } ) ; } } const startPosition = getPrecedingNonSpaceCharacterPosition ( sourceFile . text , fnStart - 1 ) ; const indent = sourceFile . text . slice ( startPosition , fnStart ) ; this . insertNodeAt ( sourceFile , fnStart , tag , { preserveLeadingWhitespace : false , suffix : this . newLineCharacter + indent } ) ; } public addJSDocTags ( sourceFile , parent , newTags ) { const comments = flatMap ( parent . jsDoc , j => typeof j . comment === "string" ? factory . createJSDocText ( j . comment ) : j . comment ) as JSDocComment [ ] ; const oldTags = flatMapToMutable ( parent . jsDoc , j => j . tags ) ; const unmergedNewTags = newTags . filter ( newTag => ! oldTags . some ( ( tag , i ) => { const merged = tryMergeJsdocTags ( tag , newTag ) ; if ( merged ) oldTags [ i ] = merged ; return ! ! merged ; } ) ) ; const tags = [ ... oldTags , ... unmergedNewTags ] ; const jsDoc = singleOrUndefined ( parent . jsDoc ) ; const comment = jsDoc && positionsAreOnSameLine ( jsDoc . pos , jsDoc . end , sourceFile ) && ! length ( comments ) ? undefined : factory . createNodeArray ( intersperse ( comments , factory . createJSDocText ( "\n" ) ) ) ; const tag = factory . createJSDocComment ( comment , factory . createNodeArray ( tags ) ) ; const host = updateJSDocHost ( parent ) ; this . insertJsdocCommentBefore ( sourceFile , host , tag ) ; } public filterJSDocTags ( sourceFile , parent , predicate ) { const comments = flatMap ( parent . jsDoc , j => typeof j . comment === "string" ? factory . createJSDocText ( j . comment ) : j . comment ) as JSDocComment [ ] ; const oldTags = flatMapToMutable ( parent . jsDoc , j => j . tags ) ; const tag = factory . createJSDocComment ( factory . createNodeArray ( intersperse ( comments , factory . createJSDocText ( "\n" ) ) ) , factory . createNodeArray ( [ ... ( filter ( oldTags , predicate ) || emptyArray ) ] ) ) ; const host = updateJSDocHost ( parent ) ; this . insertJsdocCommentBefore ( sourceFile , host , tag ) ; } public replaceRangeWithText ( sourceFile , range , text ) { this . changes . push ( { kind : ChangeKind . Text , sourceFile , range , text } ) ; } public insertText ( sourceFile , pos , text ) { this . replaceRangeWithText ( sourceFile , createRange ( pos ) , text ) ; } /** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */ public tryInsertTypeAnnotation ( sourceFile , node , type ) { let endNode ; if ( isFunctionLike ( node ) ) { endNode = findChildOfKind ( node , SyntaxKind . CloseParenToken , sourceFile ) ; if ( ! endNode ) { if ( ! isArrowFunction ( node ) ) return false ; endNode = first ( node . parameters ) ; } } else { endNode = ( node . kind === SyntaxKind . VariableDeclaration ? node . exclamationToken : node . questionToken ) ?? node . name ; } this . insertNodeAt ( sourceFile , endNode . end , type , { prefix : ": " } ) ; return true ; } public tryInsertThisTypeAnnotation ( sourceFile , node , type ) { const start = findChildOfKind ( node , SyntaxKind . OpenParenToken , sourceFile ) ! . getStart ( sourceFile ) + 1 ; const suffix = node . parameters . length ? ", " : "" ; this . insertNodeAt ( sourceFile , start , type , { prefix : "this: " , suffix } ) ; } public insertTypeParameters ( sourceFile , node , typeParameters ) { const start = ( findChildOfKind ( node , SyntaxKind . OpenParenToken , sourceFile ) || first ( node . parameters ) ) . getStart ( sourceFile ) ; this . insertNodesAt ( sourceFile , start , typeParameters , { prefix : "<" , suffix : ">" , joiner : ", " } ) ; } private getOptionsForInsertNodeBefore ( before , inserted , blankLineBetween ) { if ( isStatement ( before ) || isClassElement ( before ) ) { return { suffix : blankLineBetween ? this . newLineCharacter + this . newLineCharacter : this . newLineCharacter } ; } else if ( isVariableDeclaration ( before ) ) { return { suffix : ", " } ; } else if ( isParameter ( before ) ) { return isParameter ( inserted ) ? { suffix : ", " } : { } ; } else if ( isStringLiteral ( before ) && isImportDeclaration ( before . parent ) || isNamedImports ( before ) ) { return { suffix : ", " } ; } else if ( isImportSpecifier ( before ) ) { return { suffix : "," + ( blankLineBetween ? this . newLineCharacter : " " ) } ; } return Debug . failBadSyntaxKind ( before ) ; } public insertNodeAtConstructorStart ( sourceFile , ctr , newStatement ) { const firstStatement = firstOrUndefined ( ctr . body ! . statements ) ; if ( ! firstStatement || ! ctr . body ! . multiLine ) { this . replaceConstructorBody ( sourceFile , ctr , [ newStatement , ... ctr . body ! . statements ] ) ; } else { this . insertNodeBefore ( sourceFile , firstStatement , newStatement ) ; } } public insertNodeAtConstructorStartAfterSuperCall ( sourceFile , ctr , newStatement ) { const superCallStatement = find ( ctr . body ! . statements , stmt => isExpressionStatement ( stmt ) && isSuperCall ( stmt . expression ) ) ; if ( ! superCallStatement || ! ctr . body ! . multiLine ) { this . replaceConstructorBody ( sourceFile , ctr , [ ... ctr . body ! . statements , newStatement ] ) ; } else { this . insertNodeAfter ( sourceFile , superCallStatement , newStatement ) ; } } public insertNodeAtConstructorEnd ( sourceFile , ctr , newStatement ) { const lastStatement = lastOrUndefined ( ctr . body ! . statements ) ; if ( ! lastStatement || ! ctr . body ! . multiLine ) { this . replaceConstructorBody ( sourceFile , ctr , [ ... ctr . body ! . statements , newStatement ] ) ; } else { this . insertNodeAfter ( sourceFile , lastStatement , newStatement ) ; } } private replaceConstructorBody ( sourceFile , ctr , statements ) { this . replaceNode ( sourceFile , ctr . body ! , factory . createBlock ( statements , true ) ) ; } public insertNodeAtEndOfScope ( sourceFile , scope , newNode ) { const pos = getAdjustedStartPosition ( sourceFile , scope . getLastToken ( ) ! , { } ) ; this . insertNodeAt ( sourceFile , pos , newNode , { prefix : isLineBreak ( sourceFile . text . charCodeAt ( scope . getLastToken ( ) ! . pos ) ) ? this . newLineCharacter : this . newLineCharacter + this . newLineCharacter , suffix : this . newLineCharacter } ) ; } public insertNodeAtClassStart ( sourceFile , cls , newElement ) { this . insertNodeAtStartWorker ( sourceFile , cls , newElement ) ; } public insertNodeAtObjectStart ( sourceFile , obj , newElement ) { this . insertNodeAtStartWorker ( sourceFile , obj , newElement ) ; } private insertNodeAtStartWorker ( sourceFile , cls , newElement ) { const indentation = this . guessIndentationFromExistingMembers ( sourceFile , cls ) ?? this . computeIndentationForNewMember ( sourceFile , cls ) ; this . insertNodeAt ( sourceFile , getMembersOrProperties ( cls ) . pos , newElement , this . getInsertNodeAtStartInsertOptions ( sourceFile , cls , indentation ) ) ; } /**
         * Tries to guess the indentation from the existing members of a class/interface/object. All members must be on
         * new lines and must share the same indentation.
         */ private guessIndentationFromExistingMembers ( sourceFile , cls ) { let indentation ; let lastRange = cls ; for ( const member of getMembersOrProperties ( cls ) ) { if ( rangeStartPositionsAreOnSameLine ( lastRange , member , sourceFile ) ) { return undefined ; } const memberStart = member . getStart ( sourceFile ) ; const memberIndentation = formatting . SmartIndenter . findFirstNonWhitespaceColumn ( getLineStartPositionForPosition ( memberStart , sourceFile ) , memberStart , sourceFile , this . formatContext . options ) ; if ( indentation === undefined ) { indentation = memberIndentation ; } else if ( memberIndentation !== indentation ) { return undefined ; } lastRange = member ; } return indentation ; } private computeIndentationForNewMember ( sourceFile , cls ) { const clsStart = cls . getStart ( sourceFile ) ; return formatting . SmartIndenter . findFirstNonWhitespaceColumn ( getLineStartPositionForPosition ( clsStart , sourceFile ) , clsStart , sourceFile , this . formatContext . options ) + ( this . formatContext . options . indentSize ?? 4 ) ; } private getInsertNodeAtStartInsertOptions ( sourceFile , cls , indentation ) { const members = getMembersOrProperties ( cls ) ; const isEmpty = members . length === 0 ; const isFirstInsertion = addToSeen ( this . classesWithNodesInsertedAtStart , getNodeId ( cls ) , { node : cls , sourceFile } ) ; const insertTrailingComma = isObjectLiteralExpression ( cls ) && ( ! isJsonSourceFile ( sourceFile ) || ! isEmpty ) ; const insertLeadingComma = isObjectLiteralExpression ( cls ) && isJsonSourceFile ( sourceFile ) && isEmpty && ! isFirstInsertion ; return { indentation , prefix : ( insertLeadingComma ? "," : "" ) + this . newLineCharacter , suffix : insertTrailingComma ? "," : "" } ; } public insertNodeAfterComma ( sourceFile , after , newNode ) { const endPosition = this . insertNodeAfterWorker ( sourceFile , this . nextCommaToken ( sourceFile , after ) || after , newNode ) ; this . insertNodeAt ( sourceFile , endPosition , newNode , this . getInsertNodeAfterOptions ( sourceFile , after ) ) ; } public insertNodeAfter ( sourceFile , after , newNode ) { const endPosition = this . insertNodeAfterWorker ( sourceFile , after , newNode ) ; this . insertNodeAt ( sourceFile , endPosition , newNode , this . getInsertNodeAfterOptions ( sourceFile , after ) ) ; } public insertNodeAtEndOfList ( sourceFile , list , newNode ) { this . insertNodeAt ( sourceFile , list . end , newNode , { prefix : ", " } ) ; } public insertNodesAfter ( sourceFile , after , newNodes ) { const endPosition = this . insertNodeAfterWorker ( sourceFile , after , first ( newNodes ) ) ; this . insertNodesAt ( sourceFile , endPosition , newNodes , this . getInsertNodeAfterOptions ( sourceFile , after ) ) ; } private insertNodeAfterWorker ( sourceFile , after , newNode ) { if ( needSemicolonBetween ( after , newNode ) ) { if ( sourceFile . text . charCodeAt ( after . end - 1 ) !== CharacterCodes . semicolon ) { this . replaceRange ( sourceFile , createRange ( after . end ) , factory . createToken ( SyntaxKind . SemicolonToken ) ) ; } } const endPosition = getAdjustedEndPosition ( sourceFile , after , { } ) ; return endPosition ; } private getInsertNodeAfterOptions ( sourceFile , after ) { const options = this . getInsertNodeAfterOptionsWorker ( after ) ; return { ... options , prefix : after . end === sourceFile . end && isStatement ( after ) ? ( options . prefix ? `\n${ options . prefix }` : "\n" ) : options . prefix , } ; } private getInsertNodeAfterOptionsWorker ( node ) { switch ( node . kind ) { case SyntaxKind . ClassDeclaration : case SyntaxKind . ModuleDeclaration : return { prefix : this . newLineCharacter , suffix : this . newLineCharacter } ; case SyntaxKind . VariableDeclaration : case SyntaxKind . StringLiteral : case SyntaxKind . Identifier : return { prefix : ", " } ; case SyntaxKind . PropertyAssignment : return { suffix : "," + this . newLineCharacter } ; case SyntaxKind . ExportKeyword : return { prefix : " " } ; case SyntaxKind . Parameter : return { } ; default : Debug . assert ( isStatement ( node ) || isClassOrTypeElement ( node ) ) ; return { suffix : this . newLineCharacter } ; } } public insertName ( sourceFile , node , name ) { Debug . assert ( ! node . name ) ; if ( node . kind === SyntaxKind . ArrowFunction ) { const arrow = findChildOfKind ( node , SyntaxKind . EqualsGreaterThanToken , sourceFile ) ! ; const lparen = findChildOfKind ( node , SyntaxKind . OpenParenToken , sourceFile ) ; if ( lparen ) { this . insertNodesAt ( sourceFile , lparen . getStart ( sourceFile ) , [ factory . createToken ( SyntaxKind . FunctionKeyword ) , factory . createIdentifier ( name ) ] , { joiner : " " } ) ; deleteNode ( this , sourceFile , arrow ) ; } else { this . insertText ( sourceFile , first ( node . parameters ) . getStart ( sourceFile ) , `function ${ name }(` ) ; this . replaceRange ( sourceFile , arrow , factory . createToken ( SyntaxKind . CloseParenToken ) ) ; } if ( node . body . kind !== SyntaxKind . Block ) { this . insertNodesAt ( sourceFile , node . body . getStart ( sourceFile ) , [ factory . createToken ( SyntaxKind . OpenBraceToken ) , factory . createToken ( SyntaxKind . ReturnKeyword ) ] , { joiner : " " , suffix : " " } ) ; this . insertNodesAt ( sourceFile , node . body . end , [ factory . createToken ( SyntaxKind . SemicolonToken ) , factory . createToken ( SyntaxKind . CloseBraceToken ) ] , { joiner : " " } ) ; } } else { const pos = findChildOfKind ( node , node . kind === SyntaxKind . FunctionExpression ? SyntaxKind . FunctionKeyword : SyntaxKind . ClassKeyword , sourceFile ) ! . end ; this . insertNodeAt ( sourceFile , pos , factory . createIdentifier ( name ) , { prefix : " " } ) ; } } public insertExportModifier ( sourceFile , node ) { this . insertText ( sourceFile , node . getStart ( sourceFile ) , "export " ) ; } /**
         * This function should be used to insert nodes in lists when nodes don't carry separators as the part of the node range,
         * i.e. arguments in arguments lists, parameters in parameter lists etc.
         * Note that separators are part of the node in statements and class elements.
         */ public insertNodeInListAfter ( sourceFile , after , newNode , containingList = formatting . SmartIndenter . getContainingList ( after , sourceFile ) ) { if ( ! containingList ) { Debug . fail ( "node is not a list element" ) ; return ; } const index = indexOfNode ( containingList , after ) ; if ( index < 0 ) { return ; } const end = after . getEnd ( ) ; if ( index !== containingList . length - 1 ) { const nextToken = getTokenAtPosition ( sourceFile , after . end ) ; if ( nextToken && isSeparator ( after , nextToken ) ) { const nextNode = containingList [ index + 1 ] ; const startPos = skipWhitespacesAndLineBreaks ( sourceFile . text , nextNode . getFullStart ( ) ) ; const suffix = `${ tokenToString ( nextToken . kind ) }${ sourceFile . text . substring ( nextToken . end , startPos ) }` ; this . insertNodesAt ( sourceFile , startPos , [ newNode ] , { suffix } ) ; } } else { const afterStart = after . getStart ( sourceFile ) ; const afterStartLinePosition = getLineStartPositionForPosition ( afterStart , sourceFile ) ; let separator ; let multilineList = false ; if ( containingList . length === 1 ) { separator = SyntaxKind . CommaToken ; } else { const tokenBeforeInsertPosition = findPrecedingToken ( after . pos , sourceFile ) ; separator = isSeparator ( after , tokenBeforeInsertPosition ) ? tokenBeforeInsertPosition . kind : SyntaxKind . CommaToken ; const afterMinusOneStartLinePosition = getLineStartPositionForPosition ( containingList [ index - 1 ] . getStart ( sourceFile ) , sourceFile ) ; multilineList = afterMinusOneStartLinePosition !== afterStartLinePosition ; } if ( hasCommentsBeforeLineBreak ( sourceFile . text , after . end ) ) { multilineList = true ; } if ( multilineList ) { this . replaceRange ( sourceFile , createRange ( end ) , factory . createToken ( separator ) ) ; const indentation = formatting . SmartIndenter . findFirstNonWhitespaceColumn ( afterStartLinePosition , afterStart , sourceFile , this . formatContext . options ) ; let insertPos = skipTrivia ( sourceFile . text , end , true , false ) ; while ( insertPos !== end && isLineBreak ( sourceFile . text . charCodeAt ( insertPos - 1 ) ) ) { insertPos -- ; } this . replaceRange ( sourceFile , createRange ( insertPos ) , newNode , { indentation , prefix : this . newLineCharacter } ) ; } else { this . replaceRange ( sourceFile , createRange ( end ) , newNode , { prefix : `${ tokenToString ( separator ) } ` } ) ; } } } public parenthesizeExpression ( sourceFile , expression ) { this . replaceRange ( sourceFile , rangeOfNode ( expression ) , factory . createParenthesizedExpression ( expression ) ) ; } private finishClassesWithNodesInsertedAtStart ( ) { this . classesWithNodesInsertedAtStart . forEach ( ( { node , sourceFile } ) => { const [ openBraceEnd , closeBraceEnd ] = getClassOrObjectBraceEnds ( node , sourceFile ) ; if ( openBraceEnd !== undefined && closeBraceEnd !== undefined ) { const isEmpty = getMembersOrProperties ( node ) . length === 0 ; const isSingleLine = positionsAreOnSameLine ( openBraceEnd , closeBraceEnd , sourceFile ) ; if ( isEmpty && isSingleLine && openBraceEnd !== closeBraceEnd - 1 ) { this . deleteRange ( sourceFile , createRange ( openBraceEnd , closeBraceEnd - 1 ) ) ; } if ( isSingleLine ) { this . insertText ( sourceFile , closeBraceEnd - 1 , this . newLineCharacter ) ; } } } ) ; } private finishDeleteDeclarations ( ) { const deletedNodesInLists = new Set < Node > ( ) ; for ( const { sourceFile , node } of this . deletedNodes ) { if ( ! this . deletedNodes . some ( d => d . sourceFile === sourceFile && rangeContainsRangeExclusive ( d . node , node ) ) ) { if ( isArray ( node ) ) { this . deleteRange ( sourceFile , rangeOfTypeParameters ( sourceFile , node ) ) ; } else { deleteDeclaration . deleteDeclaration ( this , deletedNodesInLists , sourceFile , node ) ; } } } deletedNodesInLists . forEach ( node => { const sourceFile = node . getSourceFile ( ) ; const list = formatting . SmartIndenter . getContainingList ( node , sourceFile ) ! ; if ( node !== last ( list ) ) return ; const lastNonDeletedIndex = findLastIndex ( list , n => ! deletedNodesInLists . has ( n ) , list . length - 2 ) ; if ( lastNonDeletedIndex !== - 1 ) { this . deleteRange ( sourceFile , { pos : list [ lastNonDeletedIndex ] . end , end : startPositionToDeleteNodeInList ( sourceFile , list [ lastNonDeletedIndex + 1 ] ) } ) ; } } ) ; } param validate public getChanges ( validate ? ) { this . finishDeleteDeclarations ( ) ; this . finishClassesWithNodesInsertedAtStart ( ) ; 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 ) ) ; } return changes ; } public createNewFile ( oldFile , fileName , statements ) { this . newFiles . push ( { oldFile , fileName , statements } ) ; } } function updateJSDocHost ( parent ) { if ( parent . kind !== SyntaxKind . ArrowFunction ) { return parent ; } const jsDocNode = parent . parent . kind === SyntaxKind . PropertyDeclaration ? parent . parent as HasJSDoc : parent . parent . parent as HasJSDoc ; jsDocNode . jsDoc = parent . jsDoc ; jsDocNode . jsDocCache = parent . jsDocCache ; return jsDocNode ; } function tryMergeJsdocTags ( oldTag , newTag ) { if ( oldTag . kind !== newTag . kind ) { return undefined ; } switch ( oldTag . kind ) { case SyntaxKind . JSDocParameterTag : { const oldParam = oldTag as JSDocParameterTag ; const newParam = newTag as JSDocParameterTag ; return isIdentifier ( oldParam . name ) && isIdentifier ( newParam . name ) && oldParam . name . escapedText === newParam . name . escapedText ? factory . createJSDocParameterTag ( undefined , newParam . name , false , newParam . typeExpression , newParam . isNameFirst , oldParam . comment ) : undefined ; } case SyntaxKind . JSDocReturnTag : return factory . createJSDocReturnTag ( undefined , ( newTag as JSDocReturnTag ) . typeExpression , oldTag . comment ) ; case SyntaxKind . JSDocTypeTag : return factory . createJSDocTypeTag ( undefined , ( newTag as JSDocTypeTag ) . typeExpression , oldTag . comment ) ; } } function startPositionToDeleteNodeInList ( sourceFile , node ) { return skipTrivia ( sourceFile . text , getAdjustedStartPosition ( sourceFile , node , { leadingTriviaOption : LeadingTriviaOption . IncludeAll } ) , false , true ) ; } function getClassOrObjectBraceEnds ( cls , sourceFile ) { const open = findChildOfKind ( cls , SyntaxKind . OpenBraceToken , sourceFile ) ; const close = findChildOfKind ( cls , SyntaxKind . CloseBraceToken , sourceFile ) ; return [ open ?. end , close ?. end ] ; } function getMembersOrProperties ( cls ) { return isObjectLiteralExpression ( cls ) ? cls . properties : cls . members ; } export type ValidateNonFormattedText = ( node : Node , text : string ) => void ; export function getNewFileText ( statements , scriptKind , newLineCharacter , formatContext ) { return changesToText . newFileChangesWorker ( undefined , scriptKind , statements , newLineCharacter , formatContext ) ; } namespace changesToText { export function getTextChangesFromChanges ( changes , newLineCharacter , formatContext , validate ) { return mapDefined ( group ( changes , c => c . sourceFile . path ) , changesInFile => { const sourceFile = changesInFile [ 0 ] . sourceFile ; const normalized = stableSort ( changesInFile , ( a , b ) => ( a . range . pos - b . range . pos ) || ( a . range . end - b . range . end ) ) ; for ( let i = 0 ; i < normalized . length - 1 ; i ++ ) { Debug . assert ( normalized [ i ] . range . end <= normalized [ i + 1 ] . range . pos , "Changes overlap" , ( ) => `${ JSON . stringify ( normalized [ i ] . range ) } and ${ JSON . stringify ( normalized [ i + 1 ] . range ) }` ) ; } const textChanges = mapDefined ( normalized , c => { const span = createTextSpanFromRange ( c . range ) ; const newText = computeNewText ( c , sourceFile , newLineCharacter , formatContext , validate ) ; if ( span . length === newText . length && stringContainsAt ( sourceFile . text , newText , span . start ) ) { return undefined ; } return createTextChange ( span , newText ) ; } ) ; return textChanges . length > 0 ? { fileName : sourceFile . fileName , textChanges } : undefined ; } ) ; } export function newFileChanges ( oldFile , fileName , statements , newLineCharacter , formatContext ) { const text = newFileChangesWorker ( oldFile , getScriptKindFromFileName ( fileName ) , statements , newLineCharacter , formatContext ) ; return { fileName , textChanges : [ createTextChange ( createTextSpan ( 0 , 0 ) , text ) ] , isNewFile : true } ; } export function newFileChangesWorker ( oldFile , scriptKind , statements , newLineCharacter , formatContext ) { const nonFormattedText = statements . map ( s => s === SyntaxKind . NewLineTrivia ? "" : getNonformattedText ( s , oldFile , newLineCharacter ) . text ) . join ( newLineCharacter ) ; const sourceFile = createSourceFile ( "any file name" , nonFormattedText , ScriptTarget . ESNext , true , scriptKind ) ; const changes = formatting . formatDocument ( sourceFile , formatContext ) ; return applyChanges ( nonFormattedText , changes ) + newLineCharacter ; } function computeNewText ( change , sourceFile , newLineCharacter , formatContext , validate ) { if ( change . kind === ChangeKind . Remove ) { return "" ; } if ( change . kind === ChangeKind . Text ) { return change . text ; } const { options = { } , range : { pos } } = change ; const format = ( n ) => 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 ) : format ( change . node ) ; const noIndent = ( options . preserveLeadingWhitespace || options . indentation !== undefined || getLineStartPositionForPosition ( pos , sourceFile ) === pos ) ? text : text . replace ( /^\s+/ , "" ) ; return ( options . prefix || "" ) + noIndent + ( ( ! options . suffix || endsWith ( noIndent , options . suffix ) ) ? "" : options . suffix ) ; } /** Note: this may mutate `nodeIn`. */ function getFormattedTextOfNode ( nodeIn , sourceFile , pos , { indentation , prefix , delta } , newLineCharacter , formatContext , validate ) { const { node , text } = getNonformattedText ( nodeIn , sourceFile , newLineCharacter ) ; if ( validate ) validate ( node , text ) ; 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 ( formatOptions , nodeIn ) ? ( formatOptions . indentSize || 0 ) : 0 ; } const file = { text , getLineAndCharacterOfPosition ( pos ) { return getLineAndCharacterOfPosition ( this , pos ) ; } } ; 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 , sourceFile , newLineCharacter ) { const writer = createWriter ( newLineCharacter ) ; const newLine = getNewLineKind ( newLineCharacter ) ; createPrinter ( { newLine , neverAsciiEscape : true , preserveSourceNewlines : true , terminateUnterminatedLiterals : true } , writer ) . writeNode ( EmitHint . Unspecified , node , sourceFile , writer ) ; return { text : writer . getText ( ) , node : assignPositionsToNode ( node ) } ; } } export function applyChanges ( text , changes ) { for ( let i = changes . length - 1 ; i >= 0 ; i -- ) { const { span , newText } = changes [ i ] ; text = `${ text . substring ( 0 , span . start ) }${ newText }${ text . substring ( textSpanEnd ( span ) ) }` ; } return text ; } function isTrivia ( s ) { return skipTrivia ( s , 0 ) === s . length ; } export function assignPositionsToNode ( node ) { const visited = visitEachChild ( node , assignPositionsToNode , nullTransformationContext , assignPositionsToNodeArray , assignPositionsToNode ) ; const newNode = nodeIsSynthesized ( visited ) ? visited : Object . create ( visited ) as Node ; setTextRangePosEnd ( newNode , getPos ( node ) , getEnd ( node ) ) ; return newNode ; } function assignPositionsToNodeArray ( nodes , visitor , test ? , start ? , count ? ) { const visited = visitNodes ( nodes , visitor , test , start , count ) ; if ( ! visited ) { return visited ; } const nodeArray = visited === nodes ? factory . createNodeArray ( visited . slice ( 0 ) ) : visited ; setTextRangePosEnd ( nodeArray , getPos ( nodes ) , getEnd ( nodes ) ) ; return nodeArray ; } interface TextChangesWriter extends EmitTextWriter , PrintHandlers { } export function createWriter ( newLine ) { let lastNonTriviaPosition = 0 ; const writer = createTextWriter ( newLine ) ; const onBeforeEmitNode = node => { if ( node ) { setPos ( node , lastNonTriviaPosition ) ; } } ; const onAfterEmitNode = node => { if ( node ) { setEnd ( node , lastNonTriviaPosition ) ; } } ; const onBeforeEmitNodeArray = nodes => { if ( nodes ) { setPos ( nodes , lastNonTriviaPosition ) ; } } ; const onAfterEmitNodeArray = nodes => { if ( nodes ) { setEnd ( nodes , lastNonTriviaPosition ) ; } } ; const onBeforeEmitToken = node => { if ( node ) { setPos ( node , lastNonTriviaPosition ) ; } } ; const onAfterEmitToken = node => { if ( node ) { setEnd ( node , lastNonTriviaPosition ) ; } } ; function setLastNonTriviaPosition ( s , force ) { if ( force || ! isTrivia ( s ) ) { lastNonTriviaPosition = writer . getTextPos ( ) ; let i = 0 ; while ( isWhiteSpaceLike ( s . charCodeAt ( s . length - i - 1 ) ) ) { i ++ ; } lastNonTriviaPosition -= i ; } } function write ( s ) { writer . write ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeComment ( s ) { writer . writeComment ( s ) ; } function writeKeyword ( s ) { writer . writeKeyword ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeOperator ( s ) { writer . writeOperator ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writePunctuation ( s ) { writer . writePunctuation ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeTrailingSemicolon ( s ) { writer . writeTrailingSemicolon ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeParameter ( s ) { writer . writeParameter ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeProperty ( s ) { writer . writeProperty ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeSpace ( s ) { writer . writeSpace ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeStringLiteral ( s ) { writer . writeStringLiteral ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeSymbol ( s , sym ) { writer . writeSymbol ( s , sym ) ; setLastNonTriviaPosition ( s , false ) ; } function writeLine ( force ? ) { writer . writeLine ( force ) ; } function increaseIndent ( ) { writer . increaseIndent ( ) ; } function decreaseIndent ( ) { writer . decreaseIndent ( ) ; } function getText ( ) { return writer . getText ( ) ; } function rawWrite ( s ) { writer . rawWrite ( s ) ; setLastNonTriviaPosition ( s , false ) ; } function writeLiteral ( s ) { writer . writeLiteral ( s ) ; setLastNonTriviaPosition ( s , true ) ; } function getTextPos ( ) { return writer . getTextPos ( ) ; } function getLine ( ) { return writer . getLine ( ) ; } function getColumn ( ) { return writer . getColumn ( ) ; } function getIndent ( ) { return writer . getIndent ( ) ; } function isAtStartOfLine ( ) { return writer . isAtStartOfLine ( ) ; } function clear ( ) { writer . clear ( ) ; lastNonTriviaPosition = 0 ; } return { onBeforeEmitNode , onAfterEmitNode , onBeforeEmitNodeArray , onAfterEmitNodeArray , onBeforeEmitToken , onAfterEmitToken , write , writeComment , writeKeyword , writeOperator , writePunctuation , writeTrailingSemicolon , writeParameter , writeProperty , writeSpace , writeStringLiteral , writeSymbol , writeLine , increaseIndent , decreaseIndent , getText , rawWrite , writeLiteral , getTextPos , getLine , getColumn , getIndent , isAtStartOfLine , hasTrailingComment : ( ) => writer . hasTrailingComment ( ) , hasTrailingWhitespace : ( ) => writer . hasTrailingWhitespace ( ) , clear } ; } function getInsertionPositionAtSourceFileTop ( sourceFile ) { let lastPrologue ; for ( const node of sourceFile . statements ) { if ( isPrologueDirective ( node ) ) { lastPrologue = node ; } else { break ; } } let position = 0 ; const text = sourceFile . text ; if ( lastPrologue ) { position = lastPrologue . end ; advancePastLineBreak ( ) ; return position ; } const shebang = getShebang ( text ) ; if ( shebang !== undefined ) { position = shebang . length ; advancePastLineBreak ( ) ; } const ranges = getLeadingCommentRanges ( text , position ) ; if ( ! ranges ) return position ; let lastComment ; let firstNodeLine ; for ( const range of ranges ) { if ( range . kind === SyntaxKind . MultiLineCommentTrivia ) { if ( isPinnedComment ( text , range . pos ) ) { lastComment = { range , pinnedOrTripleSlash : true } ; continue ; } } else if ( isRecognizedTripleSlashComment ( text , range . pos , range . end ) ) { lastComment = { range , pinnedOrTripleSlash : true } ; continue ; } if ( lastComment ) { if ( lastComment . pinnedOrTripleSlash ) break ; const commentLine = sourceFile . getLineAndCharacterOfPosition ( range . pos ) . line ; const lastCommentEndLine = sourceFile . getLineAndCharacterOfPosition ( lastComment . range . end ) . line ; if ( commentLine >= lastCommentEndLine + 2 ) break ; } if ( sourceFile . statements . length ) { if ( firstNodeLine === undefined ) firstNodeLine = sourceFile . getLineAndCharacterOfPosition ( sourceFile . statements [ 0 ] . getStart ( ) ) . line ; const commentEndLine = sourceFile . getLineAndCharacterOfPosition ( range . end ) . line ; if ( firstNodeLine < commentEndLine + 2 ) break ; } lastComment = { range , pinnedOrTripleSlash : false } ; } if ( lastComment ) { position = lastComment . range . end ; advancePastLineBreak ( ) ; } return position ; function advancePastLineBreak ( ) { if ( position < text . length ) { const charCode = text . charCodeAt ( position ) ; if ( isLineBreak ( charCode ) ) { position ++ ; if ( position < text . length && charCode === CharacterCodes . carriageReturn && text . charCodeAt ( position ) === CharacterCodes . lineFeed ) { position ++ ; } } } } } export function isValidLocationToAddComment ( sourceFile , position ) { return ! isInComment ( sourceFile , position ) && ! isInString ( sourceFile , position ) && ! isInTemplateString ( sourceFile , position ) && ! isInJSXText ( sourceFile , position ) ; } function needSemicolonBetween ( a , b ) { return ( isPropertySignature ( a ) || isPropertyDeclaration ( a ) ) && isClassOrTypeElement ( b ) && b . name ! . kind === SyntaxKind . ComputedPropertyName || isStatementButNotDeclaration ( a ) && isStatementButNotDeclaration ( b ) ; } namespace deleteDeclaration { export function deleteDeclaration ( changes , deletedNodesInLists , sourceFile , node ) { switch ( node . kind ) { case SyntaxKind . Parameter : { const oldFunction = node . parent ; if ( isArrowFunction ( oldFunction ) && oldFunction . parameters . length === 1 && ! findChildOfKind ( oldFunction , SyntaxKind . OpenParenToken , sourceFile ) ) { changes . replaceNodeWithText ( sourceFile , node , "()" ) ; } else { deleteNodeInList ( changes , deletedNodesInLists , sourceFile , node ) ; } break ; } case SyntaxKind . ImportDeclaration : case SyntaxKind . ImportEqualsDeclaration : const isFirstImport = sourceFile . imports . length && node === first ( sourceFile . imports ) . parent || node === find ( sourceFile . statements , isAnyImportSyntax ) ; deleteNode ( changes , sourceFile , node , { leadingTriviaOption : isFirstImport ? LeadingTriviaOption . Exclude : hasJSDocNodes ( node ) ? LeadingTriviaOption . JSDoc : LeadingTriviaOption . StartLine , } ) ; break ; case SyntaxKind . BindingElement : const pattern = ( node as BindingElement ) . parent ; const preserveComma = pattern . kind === SyntaxKind . ArrayBindingPattern && node !== last ( pattern . elements ) ; if ( preserveComma ) { deleteNode ( changes , sourceFile , node ) ; } else { deleteNodeInList ( changes , deletedNodesInLists , sourceFile , node ) ; } break ; case SyntaxKind . VariableDeclaration : deleteVariableDeclaration ( changes , deletedNodesInLists , sourceFile , node as VariableDeclaration ) ; break ; case SyntaxKind . TypeParameter : deleteNodeInList ( changes , deletedNodesInLists , sourceFile , node ) ; break ; case SyntaxKind . ImportSpecifier : const namedImports = ( node as ImportSpecifier ) . parent ; if ( namedImports . elements . length === 1 ) { deleteImportBinding ( changes , sourceFile , namedImports ) ; } else { deleteNodeInList ( changes , deletedNodesInLists , sourceFile , node ) ; } break ; case SyntaxKind . NamespaceImport : deleteImportBinding ( changes , sourceFile , node as NamespaceImport ) ; break ; case SyntaxKind . SemicolonToken : deleteNode ( changes , sourceFile , node , { trailingTriviaOption : TrailingTriviaOption . Exclude } ) ; break ; case SyntaxKind . FunctionKeyword : deleteNode ( changes , sourceFile , node , { leadingTriviaOption : LeadingTriviaOption . Exclude } ) ; break ; case SyntaxKind . ClassDeclaration : case SyntaxKind . FunctionDeclaration : deleteNode ( changes , sourceFile , node , { leadingTriviaOption : hasJSDocNodes ( node ) ? LeadingTriviaOption . JSDoc : LeadingTriviaOption . StartLine } ) ; break ; default : if ( ! node . parent ) { deleteNode ( changes , sourceFile , node ) ; } else if ( isImportClause ( node . parent ) && node . parent . name === node ) { deleteDefaultImport ( changes , sourceFile , node . parent ) ; } else if ( isCallExpression ( node . parent ) && contains ( node . parent . arguments , node ) ) { deleteNodeInList ( changes , deletedNodesInLists , sourceFile , node ) ; } else { deleteNode ( changes , sourceFile , node ) ; } } } function deleteDefaultImport ( changes , sourceFile , importClause ) { if ( ! importClause . namedBindings ) { deleteNode ( changes , sourceFile , importClause . parent ) ; } else { const start = importClause . name ! . getStart ( sourceFile ) ; const nextToken = getTokenAtPosition ( sourceFile , importClause . name ! . end ) ; if ( nextToken && nextToken . kind === SyntaxKind . CommaToken ) { const end = skipTrivia ( sourceFile . text , nextToken . end , false , true ) ; changes . deleteRange ( sourceFile , { pos : start , end } ) ; } else { deleteNode ( changes , sourceFile , importClause . name ! ) ; } } } function deleteImportBinding ( changes , sourceFile , node ) { if ( node . parent . name ) { const previousToken = Debug . checkDefined ( getTokenAtPosition ( sourceFile , node . pos - 1 ) ) ; changes . deleteRange ( sourceFile , { pos : previousToken . getStart ( sourceFile ) , end : node . end } ) ; } else { const importDecl = getAncestor ( node , SyntaxKind . ImportDeclaration ) ! ; deleteNode ( changes , sourceFile , importDecl ) ; } } function deleteVariableDeclaration ( changes , deletedNodesInLists , sourceFile , node ) { const { parent } = node ; if ( parent . kind === SyntaxKind . CatchClause ) { changes . deleteNodeRange ( sourceFile , findChildOfKind ( parent , SyntaxKind . OpenParenToken , sourceFile ) ! , findChildOfKind ( parent , SyntaxKind . CloseParenToken , sourceFile ) ! ) ; return ; } if ( parent . declarations . length !== 1 ) { deleteNodeInList ( changes , deletedNodesInLists , sourceFile , node ) ; return ; } const gp = parent . parent ; switch ( gp . kind ) { case SyntaxKind . ForOfStatement : case SyntaxKind . ForInStatement : changes . replaceNode ( sourceFile , node , factory . createObjectLiteralExpression ( ) ) ; break ; case SyntaxKind . ForStatement : deleteNode ( changes , sourceFile , parent ) ; break ; case SyntaxKind . VariableStatement : deleteNode ( changes , sourceFile , gp , { leadingTriviaOption : hasJSDocNodes ( gp ) ? LeadingTriviaOption . JSDoc : LeadingTriviaOption . StartLine } ) ; break ; default : Debug . assertNever ( gp ) ; } } } /** Warning: This deletes comments too. See `copyComments` in `convertFunctionToEs6Class`. */ export function deleteNode ( changes , sourceFile , node , options = { leadingTriviaOption : LeadingTriviaOption . IncludeAll } ) { const startPosition = getAdjustedStartPosition ( sourceFile , node , options ) ; const endPosition = getAdjustedEndPosition ( sourceFile , node , options ) ; changes . deleteRange ( sourceFile , { pos : startPosition , end : endPosition } ) ; } function deleteNodeInList ( changes , deletedNodesInLists , sourceFile , node ) { const containingList = Debug . checkDefined ( formatting . SmartIndenter . getContainingList ( node , sourceFile ) ) ; const index = indexOfNode ( containingList , node ) ; Debug . assert ( index !== - 1 ) ; if ( containingList . length === 1 ) { deleteNode ( changes , sourceFile , node ) ; return ; } Debug . assert ( ! deletedNodesInLists . has ( node ) , "Deleting a node twice" ) ; deletedNodesInLists . add ( node ) ; changes . deleteRange ( sourceFile , { pos : startPositionToDeleteNodeInList ( sourceFile , node ) , end : index === containingList . length - 1 ? getAdjustedEndPosition ( sourceFile , node , { } ) : startPositionToDeleteNodeInList ( sourceFile , containingList [ index + 1 ] ) , } ) ; } }