namespace ts . formatting { export interface FormatContext { readonly options : FormatCodeSettings ; readonly getRules : RulesMap ; readonly host : FormattingHost ; } export interface TextRangeWithKind < T extends SyntaxKind = SyntaxKind > extends TextRange { kind : T ; } export type TextRangeWithTriviaKind = TextRangeWithKind < TriviaSyntaxKind > ; export interface TokenInfo { leadingTrivia : TextRangeWithTriviaKind [ ] | undefined ; token : TextRangeWithKind ; trailingTrivia : TextRangeWithTriviaKind [ ] | undefined ; } export function createTextRangeWithKind < T extends SyntaxKind > ( pos , end , kind ) { const textRangeWithKind = { pos , end , kind } ; if ( Debug . isDebugging ) { Object . defineProperty ( textRangeWithKind , "__debugKind" , { get : ( ) => Debug . formatSyntaxKind ( kind ) , } ) ; } return textRangeWithKind ; } const enum Constants { Unknown = - 1 } interface DynamicIndentation { getIndentationForToken ( tokenLine , tokenKind , container , suppressDelta ) : number ; getIndentationForComment ( owningToken , tokenIndentation , container ) : number ; /**
         * Indentation for open and close tokens of the node if it is block or another node that needs special indentation
         * ... {
         * .........<child>
         * ....}
         *  ____ - indentation
         *      ____ - delta
         */ getIndentation ( ) : number ; /**
         * Prefered relative indentation for child nodes.
         * Delta is used to carry the indentation info
         * foo(bar({
         *     $
         * }))
         * Both 'foo', 'bar' introduce new indentation with delta = 4, but total indentation in $ is not 8.
         * foo: { indentation: 0, delta: 4 }
         * bar: { indentation: foo.indentation + foo.delta = 4, delta: 4} however 'foo' and 'bar' are on the same line
         * so bar inherits indentation from foo and bar.delta will be 4
         *
         */ getDelta ( child ) : number ; /**
         * Formatter calls this function when rule adds or deletes new lines from the text
         * so indentation scope can adjust values of indentation and delta.
         */ recomputeIndentation ( lineAddedByFormatting , parent ) : void ; } export function formatOnEnter ( position , sourceFile , formatContext ) { const line = sourceFile . getLineAndCharacterOfPosition ( position ) . line ; if ( line === 0 ) { return [ ] ; } let endOfFormatSpan = getEndLinePosition ( line , sourceFile ) ; while ( isWhiteSpaceSingleLine ( sourceFile . text . charCodeAt ( endOfFormatSpan ) ) ) { endOfFormatSpan -- ; } if ( isLineBreak ( sourceFile . text . charCodeAt ( endOfFormatSpan ) ) ) { endOfFormatSpan -- ; } const span = { pos : getStartPositionOfLine ( line - 1 , sourceFile ) , end : endOfFormatSpan + 1 } ; return formatSpan ( span , sourceFile , formatContext , FormattingRequestKind . FormatOnEnter ) ; } export function formatOnSemicolon ( position , sourceFile , formatContext ) { const semicolon = findImmediatelyPrecedingTokenOfKind ( position , SyntaxKind . SemicolonToken , sourceFile ) ; return formatNodeLines ( findOutermostNodeWithinListLevel ( semicolon ) , sourceFile , formatContext , FormattingRequestKind . FormatOnSemicolon ) ; } export function formatOnOpeningCurly ( position , sourceFile , formatContext ) { const openingCurly = findImmediatelyPrecedingTokenOfKind ( position , SyntaxKind . OpenBraceToken , sourceFile ) ; if ( ! openingCurly ) { return [ ] ; } const curlyBraceRange = openingCurly . parent ; const outermostNode = findOutermostNodeWithinListLevel ( curlyBraceRange ) ; /**
         * We limit the span to end at the opening curly to handle the case where
         * the brace matched to that just typed will be incorrect after further edits.
         * For example, we could type the opening curly for the following method
         * body without brace-matching activated:
         * ```
         * class C {
         *     foo()
         * }
         * ```
         * and we wouldn't want to move the closing brace.
         */ const textRange = { pos : getLineStartPositionForPosition ( outermostNode ! . getStart ( sourceFile ) , sourceFile ) , end : position } ; return formatSpan ( textRange , sourceFile , formatContext , FormattingRequestKind . FormatOnOpeningCurlyBrace ) ; } export function formatOnClosingCurly ( position , sourceFile , formatContext ) { const precedingToken = findImmediatelyPrecedingTokenOfKind ( position , SyntaxKind . CloseBraceToken , sourceFile ) ; return formatNodeLines ( findOutermostNodeWithinListLevel ( precedingToken ) , sourceFile , formatContext , FormattingRequestKind . FormatOnClosingCurlyBrace ) ; } export function formatDocument ( sourceFile , formatContext ) { const span = { pos : 0 , end : sourceFile . text . length } ; return formatSpan ( span , sourceFile , formatContext , FormattingRequestKind . FormatDocument ) ; } export function formatSelection ( start , end , sourceFile , formatContext ) { const span = { pos : getLineStartPositionForPosition ( start , sourceFile ) , end , } ; return formatSpan ( span , sourceFile , formatContext , FormattingRequestKind . FormatSelection ) ; } param expectedTokenKind function findImmediatelyPrecedingTokenOfKind ( end , expectedTokenKind , sourceFile ) { const precedingToken = findPrecedingToken ( end , sourceFile ) ; return precedingToken && precedingToken . kind === expectedTokenKind && end === precedingToken . getEnd ( ) ? precedingToken : undefined ; } /**
     * Finds the highest node enclosing `node` at the same list level as `node`
     * and whose end does not exceed `node.end`.
     *
     * Consider typing the following
     * ```
     * let x = 1;
     * while (true) {
     * }
     * ```
     * Upon typing the closing curly, we want to format the entire `while`-statement, but not the preceding
     * variable declaration.
     */ function findOutermostNodeWithinListLevel ( node ) { let current = node ; while ( current && current . parent && current . parent . end === node ! . end && ! isListElement ( current . parent , current ) ) { current = current . parent ; } return current ; } function isListElement ( parent , node ) { switch ( parent . kind ) { case SyntaxKind . ClassDeclaration : case SyntaxKind . InterfaceDeclaration : return rangeContainsRange ( ( parent as InterfaceDeclaration ) . members , node ) ; case SyntaxKind . ModuleDeclaration : const body = ( parent as ModuleDeclaration ) . body ; return ! ! body && body . kind === SyntaxKind . ModuleBlock && rangeContainsRange ( body . statements , node ) ; case SyntaxKind . SourceFile : case SyntaxKind . Block : case SyntaxKind . ModuleBlock : return rangeContainsRange ( ( parent as Block ) . statements , node ) ; case SyntaxKind . CatchClause : return rangeContainsRange ( ( parent as CatchClause ) . block . statements , node ) ; } return false ; } /** find node that fully contains given text range */ function findEnclosingNode ( range , sourceFile ) { return find ( sourceFile ) ; function find ( n ) { const candidate = forEachChild ( n , c => startEndContainsRange ( c . getStart ( sourceFile ) , c . end , range ) && c ) ; if ( candidate ) { const result = find ( candidate ) ; if ( result ) { return result ; } } return n ; } } /** formatting is not applied to ranges that contain parse errors.
     * This function will return a predicate that for a given text range will tell
     * if there are any parse errors that overlap with the range.
     */ function prepareRangeContainsErrorFunction ( errors , originalRange ) { if ( ! errors . length ) { return rangeHasNoErrors ; } const sorted = errors . filter ( d => rangeOverlapsWithStartEnd ( originalRange , d . start ! , d . start ! + d . length ! ) ) . sort ( ( e1 , e2 ) => e1 . start ! - e2 . start ! ) ; if ( ! sorted . length ) { return rangeHasNoErrors ; } let index = 0 ; return r => { while ( true ) { if ( index >= sorted . length ) { return false ; } const error = sorted [ index ] ; if ( r . end <= error . start ! ) { return false ; } if ( startEndOverlapsWithStartEnd ( r . pos , r . end , error . start ! , error . start ! + error . length ! ) ) { return true ; } index ++ ; } } ; function rangeHasNoErrors ( ) { return false ; } } /**
     * Start of the original range might fall inside the comment - scanner will not yield appropriate results
     * This function will look for token that is located before the start of target range
     * and return its end as start position for the scanner.
     */ function getScanStartPosition ( enclosingNode , originalRange , sourceFile ) { const start = enclosingNode . getStart ( sourceFile ) ; if ( start === originalRange . pos && enclosingNode . end === originalRange . end ) { return start ; } const precedingToken = findPrecedingToken ( originalRange . pos , sourceFile ) ; if ( ! precedingToken ) { return enclosingNode . pos ; } if ( precedingToken . end >= originalRange . pos ) { return enclosingNode . pos ; } return precedingToken . end ; } function getOwnOrInheritedDelta ( n , options , sourceFile ) { let previousLine = Constants . Unknown ; let child ; while ( n ) { const line = sourceFile . getLineAndCharacterOfPosition ( n . getStart ( sourceFile ) ) . line ; if ( previousLine !== Constants . Unknown && line !== previousLine ) { break ; } if ( SmartIndenter . shouldIndentChildNode ( options , n , child , sourceFile ) ) { return options . indentSize ! ; } previousLine = line ; child = n ; n = n . parent ; } return 0 ; } export function formatNodeGivenIndentation ( node , sourceFileLike , languageVariant , initialIndentation , delta , formatContext ) { const range = { pos : node . pos , end : node . end } ; return getFormattingScanner ( sourceFileLike . text , languageVariant , range . pos , range . end , scanner => formatSpanWorker ( range , node , initialIndentation , delta , scanner , formatContext , FormattingRequestKind . FormatSelection , _ => false , sourceFileLike ) ) ; } function formatNodeLines ( node , sourceFile , formatContext , requestKind ) { if ( ! node ) { return [ ] ; } const span = { pos : getLineStartPositionForPosition ( node . getStart ( sourceFile ) , sourceFile ) , end : node . end } ; return formatSpan ( span , sourceFile , formatContext , requestKind ) ; } function formatSpan ( originalRange , sourceFile , formatContext , requestKind ) { const enclosingNode = findEnclosingNode ( originalRange , sourceFile ) ; return getFormattingScanner ( sourceFile . text , sourceFile . languageVariant , getScanStartPosition ( enclosingNode , originalRange , sourceFile ) , originalRange . end , scanner => formatSpanWorker ( originalRange , enclosingNode , SmartIndenter . getIndentationForNode ( enclosingNode , originalRange , sourceFile , formatContext . options ) , getOwnOrInheritedDelta ( enclosingNode , formatContext . options , sourceFile ) , scanner , formatContext , requestKind , prepareRangeContainsErrorFunction ( sourceFile . parseDiagnostics , originalRange ) , sourceFile ) ) ; } function formatSpanWorker ( originalRange , enclosingNode , initialIndentation , delta , formattingScanner , { options , getRules , host } , requestKind , rangeContainsError , sourceFile ) { const formattingContext = new FormattingContext ( sourceFile , requestKind , options ) ; let previousRange ; let previousParent ; let previousRangeStartLine ; let lastIndentedLine ; let indentationOnLastIndentedLine = Constants . Unknown ; const edits = [ ] ; formattingScanner . advance ( ) ; if ( formattingScanner . isOnToken ( ) ) { const startLine = sourceFile . getLineAndCharacterOfPosition ( enclosingNode . getStart ( sourceFile ) ) . line ; let undecoratedStartLine = startLine ; if ( enclosingNode . decorators ) { undecoratedStartLine = sourceFile . getLineAndCharacterOfPosition ( getNonDecoratorTokenPosOfNode ( enclosingNode , sourceFile ) ) . line ; } processNode ( enclosingNode , enclosingNode , startLine , undecoratedStartLine , initialIndentation , delta ) ; } if ( ! formattingScanner . isOnToken ( ) ) { const indentation = SmartIndenter . nodeWillIndentChild ( options , enclosingNode , undefined , sourceFile , false ) ? initialIndentation + options . indentSize ! : initialIndentation ; const leadingTrivia = formattingScanner . getCurrentLeadingTrivia ( ) ; if ( leadingTrivia ) { indentTriviaItems ( leadingTrivia , indentation , false , item => processRange ( item , sourceFile . getLineAndCharacterOfPosition ( item . pos ) , enclosingNode , enclosingNode , undefined ! ) ) ; if ( options . trimTrailingWhitespace !== false ) { trimTrailingWhitespacesForRemainingRange ( leadingTrivia ) ; } } } if ( previousRange ! && formattingScanner . getStartPos ( ) >= originalRange . end ) { const token = formattingScanner . isOnEOF ( ) ? formattingScanner . readEOFTokenRange ( ) : formattingScanner . isOnToken ( ) ? formattingScanner . readTokenInfo ( enclosingNode ) . token : undefined ; if ( token ) { processPair ( token , sourceFile . getLineAndCharacterOfPosition ( token . pos ) . line , enclosingNode , previousRange , previousRangeStartLine ! , previousParent ! , enclosingNode , undefined ) ; } } return edits ; /** Tries to compute the indentation for a list element.
         * If list element is not in range then
         * function will pick its actual indentation
         * so it can be pushed downstream as inherited indentation.
         * If list element is in the range - its indentation will be equal
         * to inherited indentation from its predecessors.
         */ function tryComputeIndentationForListItem ( startPos , endPos , parentStartLine , range , inheritedIndentation ) { if ( rangeOverlapsWithStartEnd ( range , startPos , endPos ) || rangeContainsStartEnd ( range , startPos , endPos ) ) { if ( inheritedIndentation !== Constants . Unknown ) { return inheritedIndentation ; } } else { const startLine = sourceFile . getLineAndCharacterOfPosition ( startPos ) . line ; const startLinePosition = getLineStartPositionForPosition ( startPos , sourceFile ) ; const column = SmartIndenter . findFirstNonWhitespaceColumn ( startLinePosition , startPos , sourceFile , options ) ; if ( startLine !== parentStartLine || startPos === column ) { const baseIndentSize = SmartIndenter . getBaseIndentation ( options ) ; return baseIndentSize > column ? baseIndentSize : column ; } } return Constants . Unknown ; } function computeIndentation ( node , startLine , inheritedIndentation , parent , parentDynamicIndentation , effectiveParentStartLine ) { const delta = SmartIndenter . shouldIndentChildNode ( options , node ) ? options . indentSize ! : 0 ; if ( effectiveParentStartLine === startLine ) { return { indentation : startLine === lastIndentedLine ? indentationOnLastIndentedLine : parentDynamicIndentation . getIndentation ( ) , delta : Math . min ( options . indentSize ! , parentDynamicIndentation . getDelta ( node ) + delta ) } ; } else if ( inheritedIndentation === Constants . Unknown ) { if ( node . kind === SyntaxKind . OpenParenToken && startLine === lastIndentedLine ) { return { indentation : indentationOnLastIndentedLine , delta : parentDynamicIndentation . getDelta ( node ) } ; } else if ( SmartIndenter . childStartsOnTheSameLineWithElseInIfStatement ( parent , node , startLine , sourceFile ) || SmartIndenter . childIsUnindentedBranchOfConditionalExpression ( parent , node , startLine , sourceFile ) || SmartIndenter . argumentStartsOnSameLineAsPreviousArgument ( parent , node , startLine , sourceFile ) ) { return { indentation : parentDynamicIndentation . getIndentation ( ) , delta } ; } else { return { indentation : parentDynamicIndentation . getIndentation ( ) + parentDynamicIndentation . getDelta ( node ) , delta } ; } } else { return { indentation : inheritedIndentation , delta } ; } } function getFirstNonDecoratorTokenOfNode ( node ) { if ( node . modifiers && node . modifiers . length ) { return node . modifiers [ 0 ] . kind ; } switch ( node . kind ) { case SyntaxKind . ClassDeclaration : return SyntaxKind . ClassKeyword ; case SyntaxKind . InterfaceDeclaration : return SyntaxKind . InterfaceKeyword ; case SyntaxKind . FunctionDeclaration : return SyntaxKind . FunctionKeyword ; case SyntaxKind . EnumDeclaration : return SyntaxKind . EnumDeclaration ; case SyntaxKind . GetAccessor : return SyntaxKind . GetKeyword ; case SyntaxKind . SetAccessor : return SyntaxKind . SetKeyword ; case SyntaxKind . MethodDeclaration : if ( ( node as MethodDeclaration ) . asteriskToken ) { return SyntaxKind . AsteriskToken ; } case SyntaxKind . PropertyDeclaration : case SyntaxKind . Parameter : const name = getNameOfDeclaration ( node as Declaration ) ; if ( name ) { return name . kind ; } } } function getDynamicIndentation ( node , nodeStartLine , indentation , delta ) { return { getIndentationForComment : ( kind , tokenIndentation , container ) => { switch ( kind ) { case SyntaxKind . CloseBraceToken : case SyntaxKind . CloseBracketToken : case SyntaxKind . CloseParenToken : return indentation + getDelta ( container ) ; } return tokenIndentation !== Constants . Unknown ? tokenIndentation : indentation ; } , getIndentationForToken : ( line , kind , container , suppressDelta ) => ! suppressDelta && shouldAddDelta ( line , kind , container ) ? indentation + getDelta ( container ) : indentation , getIndentation : ( ) => indentation , getDelta , recomputeIndentation : ( lineAdded , parent ) => { if ( SmartIndenter . shouldIndentChildNode ( options , parent , node , sourceFile ) ) { indentation += lineAdded ? options . indentSize ! : - options . indentSize ! ; delta = SmartIndenter . shouldIndentChildNode ( options , node ) ? options . indentSize ! : 0 ; } } } ; function shouldAddDelta ( line , kind , container ) { switch ( kind ) { case SyntaxKind . OpenBraceToken : case SyntaxKind . CloseBraceToken : case SyntaxKind . CloseParenToken : case SyntaxKind . ElseKeyword : case SyntaxKind . WhileKeyword : case SyntaxKind . AtToken : return false ; case SyntaxKind . SlashToken : case SyntaxKind . GreaterThanToken : switch ( container . kind ) { case SyntaxKind . JsxOpeningElement : case SyntaxKind . JsxClosingElement : case SyntaxKind . JsxSelfClosingElement : case SyntaxKind . ExpressionWithTypeArguments : return false ; } break ; case SyntaxKind . OpenBracketToken : case SyntaxKind . CloseBracketToken : if ( container . kind !== SyntaxKind . MappedType ) { return false ; } break ; } return nodeStartLine !== line && ! ( node . decorators && kind === getFirstNonDecoratorTokenOfNode ( node ) ) ; } function getDelta ( child ) { return SmartIndenter . nodeWillIndentChild ( options , node , child , sourceFile , true ) ? delta : 0 ; } } function processNode ( node , contextNode , nodeStartLine , undecoratedNodeStartLine , indentation , delta ) { if ( ! rangeOverlapsWithStartEnd ( originalRange , node . getStart ( sourceFile ) , node . getEnd ( ) ) ) { return ; } const nodeDynamicIndentation = getDynamicIndentation ( node , nodeStartLine , indentation , delta ) ; let childContextNode = contextNode ; forEachChild ( node , child => { processChildNode ( child , Constants . Unknown , node , nodeDynamicIndentation , nodeStartLine , undecoratedNodeStartLine , false ) ; } , nodes => { processChildNodes ( nodes , node , nodeStartLine , nodeDynamicIndentation ) ; } ) ; while ( formattingScanner . isOnToken ( ) && formattingScanner . getStartPos ( ) < originalRange . end ) { const tokenInfo = formattingScanner . readTokenInfo ( node ) ; if ( tokenInfo . token . end > Math . min ( node . end , originalRange . end ) ) { break ; } consumeTokenAndAdvanceScanner ( tokenInfo , node , nodeDynamicIndentation , node ) ; } function processChildNode ( child , inheritedIndentation , parent , parentDynamicIndentation , parentStartLine , undecoratedParentStartLine , isListItem , isFirstListItem ? ) { const childStartPos = child . getStart ( sourceFile ) ; const childStartLine = sourceFile . getLineAndCharacterOfPosition ( childStartPos ) . line ; let undecoratedChildStartLine = childStartLine ; if ( child . decorators ) { undecoratedChildStartLine = sourceFile . getLineAndCharacterOfPosition ( getNonDecoratorTokenPosOfNode ( child , sourceFile ) ) . line ; } let childIndentationAmount = Constants . Unknown ; if ( isListItem && rangeContainsRange ( originalRange , parent ) ) { childIndentationAmount = tryComputeIndentationForListItem ( childStartPos , child . end , parentStartLine , originalRange , inheritedIndentation ) ; if ( childIndentationAmount !== Constants . Unknown ) { inheritedIndentation = childIndentationAmount ; } } if ( ! rangeOverlapsWithStartEnd ( originalRange , child . pos , child . end ) ) { if ( child . end < originalRange . pos ) { formattingScanner . skipToEndOf ( child ) ; } return inheritedIndentation ; } if ( child . getFullWidth ( ) === 0 ) { return inheritedIndentation ; } while ( formattingScanner . isOnToken ( ) && formattingScanner . getStartPos ( ) < originalRange . end ) { const tokenInfo = formattingScanner . readTokenInfo ( node ) ; if ( tokenInfo . token . end > originalRange . end ) { return inheritedIndentation ; } if ( tokenInfo . token . end > childStartPos ) { if ( tokenInfo . token . pos > childStartPos ) { formattingScanner . skipToStartOf ( child ) ; } break ; } consumeTokenAndAdvanceScanner ( tokenInfo , node , parentDynamicIndentation , node ) ; } if ( ! formattingScanner . isOnToken ( ) || formattingScanner . getStartPos ( ) >= originalRange . end ) { return inheritedIndentation ; } if ( isToken ( child ) ) { const tokenInfo = formattingScanner . readTokenInfo ( child ) ; if ( child . kind !== SyntaxKind . JsxText ) { Debug . assert ( tokenInfo . token . end === child . end , "Token end is child end" ) ; consumeTokenAndAdvanceScanner ( tokenInfo , node , parentDynamicIndentation , child ) ; return inheritedIndentation ; } } const effectiveParentStartLine = child . kind === SyntaxKind . Decorator ? childStartLine : undecoratedParentStartLine ; const childIndentation = computeIndentation ( child , childStartLine , childIndentationAmount , node , parentDynamicIndentation , effectiveParentStartLine ) ; processNode ( child , childContextNode , childStartLine , undecoratedChildStartLine , childIndentation . indentation , childIndentation . delta ) ; childContextNode = node ; if ( isFirstListItem && parent . kind === SyntaxKind . ArrayLiteralExpression && inheritedIndentation === Constants . Unknown ) { inheritedIndentation = childIndentation . indentation ; } return inheritedIndentation ; } function processChildNodes ( nodes , parent , parentStartLine , parentDynamicIndentation ) { Debug . assert ( isNodeArray ( nodes ) ) ; const listStartToken = getOpenTokenForList ( parent , nodes ) ; let listDynamicIndentation = parentDynamicIndentation ; let startLine = parentStartLine ; if ( listStartToken !== SyntaxKind . Unknown ) { while ( formattingScanner . isOnToken ( ) && formattingScanner . getStartPos ( ) < originalRange . end ) { const tokenInfo = formattingScanner . readTokenInfo ( parent ) ; if ( tokenInfo . token . end > nodes . pos ) { break ; } else if ( tokenInfo . token . kind === listStartToken ) { startLine = sourceFile . getLineAndCharacterOfPosition ( tokenInfo . token . pos ) . line ; consumeTokenAndAdvanceScanner ( tokenInfo , parent , parentDynamicIndentation , parent ) ; let indentationOnListStartToken ; if ( indentationOnLastIndentedLine !== Constants . Unknown ) { indentationOnListStartToken = indentationOnLastIndentedLine ; } else { const startLinePosition = getLineStartPositionForPosition ( tokenInfo . token . pos , sourceFile ) ; indentationOnListStartToken = SmartIndenter . findFirstNonWhitespaceColumn ( startLinePosition , tokenInfo . token . pos , sourceFile , options ) ; } listDynamicIndentation = getDynamicIndentation ( parent , parentStartLine , indentationOnListStartToken , options . indentSize ! ) ; } else { consumeTokenAndAdvanceScanner ( tokenInfo , parent , parentDynamicIndentation , parent ) ; } } } let inheritedIndentation = Constants . Unknown ; for ( let i = 0 ; i < nodes . length ; i ++ ) { const child = nodes [ i ] ; inheritedIndentation = processChildNode ( child , inheritedIndentation , node , listDynamicIndentation , startLine , startLine , true , i === 0 ) ; } const listEndToken = getCloseTokenForOpenToken ( listStartToken ) ; if ( listEndToken !== SyntaxKind . Unknown && formattingScanner . isOnToken ( ) && formattingScanner . getStartPos ( ) < originalRange . end ) { let tokenInfo = formattingScanner . readTokenInfo ( parent ) ; if ( tokenInfo . token . kind === SyntaxKind . CommaToken && isCallLikeExpression ( parent ) ) { const commaTokenLine = sourceFile . getLineAndCharacterOfPosition ( tokenInfo . token . pos ) . line ; if ( startLine !== commaTokenLine ) { formattingScanner . advance ( ) ; tokenInfo = formattingScanner . isOnToken ( ) ? formattingScanner . readTokenInfo ( parent ) : undefined ; } } if ( tokenInfo && tokenInfo . token . kind === listEndToken && rangeContainsRange ( parent , tokenInfo . token ) ) { consumeTokenAndAdvanceScanner ( tokenInfo , parent , listDynamicIndentation , parent , true ) ; } } } function consumeTokenAndAdvanceScanner ( currentTokenInfo , parent , dynamicIndentation , container , isListEndToken ? ) { Debug . assert ( rangeContainsRange ( parent , currentTokenInfo . token ) ) ; const lastTriviaWasNewLine = formattingScanner . lastTrailingTriviaWasNewLine ( ) ; let indentToken = false ; if ( currentTokenInfo . leadingTrivia ) { processTrivia ( currentTokenInfo . leadingTrivia , parent , childContextNode , dynamicIndentation ) ; } let lineAction = LineAction . None ; const isTokenInRange = rangeContainsRange ( originalRange , currentTokenInfo . token ) ; const tokenStart = sourceFile . getLineAndCharacterOfPosition ( currentTokenInfo . token . pos ) ; if ( isTokenInRange ) { const rangeHasError = rangeContainsError ( currentTokenInfo . token ) ; const savePreviousRange = previousRange ; lineAction = processRange ( currentTokenInfo . token , tokenStart , parent , childContextNode , dynamicIndentation ) ; if ( ! rangeHasError ) { if ( lineAction === LineAction . None ) { const prevEndLine = savePreviousRange && sourceFile . getLineAndCharacterOfPosition ( savePreviousRange . end ) . line ; indentToken = lastTriviaWasNewLine && tokenStart . line !== prevEndLine ; } else { indentToken = lineAction === LineAction . LineAdded ; } } } if ( currentTokenInfo . trailingTrivia ) { processTrivia ( currentTokenInfo . trailingTrivia , parent , childContextNode , dynamicIndentation ) ; } if ( indentToken ) { const tokenIndentation = ( isTokenInRange && ! rangeContainsError ( currentTokenInfo . token ) ) ? dynamicIndentation . getIndentationForToken ( tokenStart . line , currentTokenInfo . token . kind , container , ! ! isListEndToken ) : Constants . Unknown ; let indentNextTokenOrTrivia = true ; if ( currentTokenInfo . leadingTrivia ) { const commentIndentation = dynamicIndentation . getIndentationForComment ( currentTokenInfo . token . kind , tokenIndentation , container ) ; indentNextTokenOrTrivia = indentTriviaItems ( currentTokenInfo . leadingTrivia , commentIndentation , indentNextTokenOrTrivia , item => insertIndentation ( item . pos , commentIndentation , false ) ) ; } if ( tokenIndentation !== Constants . Unknown && indentNextTokenOrTrivia ) { insertIndentation ( currentTokenInfo . token . pos , tokenIndentation , lineAction === LineAction . LineAdded ) ; lastIndentedLine = tokenStart . line ; indentationOnLastIndentedLine = tokenIndentation ; } } formattingScanner . advance ( ) ; childContextNode = parent ; } } function indentTriviaItems ( trivia , commentIndentation , indentNextTokenOrTrivia , indentSingleLine ) { for ( const triviaItem of trivia ) { const triviaInRange = rangeContainsRange ( originalRange , triviaItem ) ; switch ( triviaItem . kind ) { case SyntaxKind . MultiLineCommentTrivia : if ( triviaInRange ) { indentMultilineComment ( triviaItem , commentIndentation , ! indentNextTokenOrTrivia ) ; } indentNextTokenOrTrivia = false ; break ; case SyntaxKind . SingleLineCommentTrivia : if ( indentNextTokenOrTrivia && triviaInRange ) { indentSingleLine ( triviaItem ) ; } indentNextTokenOrTrivia = false ; break ; case SyntaxKind . NewLineTrivia : indentNextTokenOrTrivia = true ; break ; } } return indentNextTokenOrTrivia ; } function processTrivia ( trivia , parent , contextNode , dynamicIndentation ) { for ( const triviaItem of trivia ) { if ( isComment ( triviaItem . kind ) && rangeContainsRange ( originalRange , triviaItem ) ) { const triviaItemStart = sourceFile . getLineAndCharacterOfPosition ( triviaItem . pos ) ; processRange ( triviaItem , triviaItemStart , parent , contextNode , dynamicIndentation ) ; } } } function processRange ( range , rangeStart , parent , contextNode , dynamicIndentation ) { const rangeHasError = rangeContainsError ( range ) ; let lineAction = LineAction . None ; if ( ! rangeHasError ) { if ( ! previousRange ) { const originalStart = sourceFile . getLineAndCharacterOfPosition ( originalRange . pos ) ; trimTrailingWhitespacesForLines ( originalStart . line , rangeStart . line ) ; } else { lineAction = processPair ( range , rangeStart . line , parent , previousRange , previousRangeStartLine , previousParent , contextNode , dynamicIndentation ) ; } } previousRange = range ; previousParent = parent ; previousRangeStartLine = rangeStart . line ; return lineAction ; } function processPair ( currentItem , currentStartLine , currentParent , previousItem , previousStartLine , previousParent , contextNode , dynamicIndentation ) { formattingContext . updateContext ( previousItem , previousParent , currentItem , currentParent , contextNode ) ; const rules = getRules ( formattingContext ) ; let trimTrailingWhitespaces = formattingContext . options . trimTrailingWhitespace !== false ; let lineAction = LineAction . None ; if ( rules ) { forEachRight ( rules , rule => { lineAction = applyRuleEdits ( rule , previousItem , previousStartLine , currentItem , currentStartLine ) ; if ( dynamicIndentation ) { switch ( lineAction ) { case LineAction . LineRemoved : if ( currentParent . getStart ( sourceFile ) === currentItem . pos ) { dynamicIndentation . recomputeIndentation ( false , contextNode ) ; } break ; case LineAction . LineAdded : if ( currentParent . getStart ( sourceFile ) === currentItem . pos ) { dynamicIndentation . recomputeIndentation ( true , contextNode ) ; } break ; default : Debug . assert ( lineAction === LineAction . None ) ; } } trimTrailingWhitespaces = trimTrailingWhitespaces && ! ( rule . action & RuleAction . DeleteSpace ) && rule . flags !== RuleFlags . CanDeleteNewLines ; } ) ; } else { trimTrailingWhitespaces = trimTrailingWhitespaces && currentItem . kind !== SyntaxKind . EndOfFileToken ; } if ( currentStartLine !== previousStartLine && trimTrailingWhitespaces ) { trimTrailingWhitespacesForLines ( previousStartLine , currentStartLine , previousItem ) ; } return lineAction ; } function insertIndentation ( pos , indentation , lineAdded ) { const indentationString = getIndentationString ( indentation , options ) ; if ( lineAdded ) { recordReplace ( pos , 0 , indentationString ) ; } else { const tokenStart = sourceFile . getLineAndCharacterOfPosition ( pos ) ; const startLinePosition = getStartPositionOfLine ( tokenStart . line , sourceFile ) ; if ( indentation !== characterToColumn ( startLinePosition , tokenStart . character ) || indentationIsDifferent ( indentationString , startLinePosition ) ) { recordReplace ( startLinePosition , tokenStart . character , indentationString ) ; } } } function characterToColumn ( startLinePosition , characterInLine ) { let column = 0 ; for ( let i = 0 ; i < characterInLine ; i ++ ) { if ( sourceFile . text . charCodeAt ( startLinePosition + i ) === CharacterCodes . tab ) { column += options . tabSize ! - column % options . tabSize ! ; } else { column ++ ; } } return column ; } function indentationIsDifferent ( indentationString , startLinePosition ) { return indentationString !== sourceFile . text . substr ( startLinePosition , indentationString . length ) ; } function indentMultilineComment ( commentRange , indentation , firstLineIsIndented , indentFinalLine = true ) { let startLine = sourceFile . getLineAndCharacterOfPosition ( commentRange . pos ) . line ; const endLine = sourceFile . getLineAndCharacterOfPosition ( commentRange . end ) . line ; if ( startLine === endLine ) { if ( ! firstLineIsIndented ) { insertIndentation ( commentRange . pos , indentation , false ) ; } return ; } const parts = [ ] ; let startPos = commentRange . pos ; for ( let line = startLine ; line < endLine ; line ++ ) { const endOfLine = getEndLinePosition ( line , sourceFile ) ; parts . push ( { pos : startPos , end : endOfLine } ) ; startPos = getStartPositionOfLine ( line + 1 , sourceFile ) ; } if ( indentFinalLine ) { parts . push ( { pos : startPos , end : commentRange . end } ) ; } if ( parts . length === 0 ) return ; const startLinePos = getStartPositionOfLine ( startLine , sourceFile ) ; const nonWhitespaceColumnInFirstPart = SmartIndenter . findFirstNonWhitespaceCharacterAndColumn ( startLinePos , parts [ 0 ] . pos , sourceFile , options ) ; let startIndex = 0 ; if ( firstLineIsIndented ) { startIndex = 1 ; startLine ++ ; } const delta = indentation - nonWhitespaceColumnInFirstPart . column ; for ( let i = startIndex ; i < parts . length ; i ++ , startLine ++ ) { const startLinePos = getStartPositionOfLine ( startLine , sourceFile ) ; const nonWhitespaceCharacterAndColumn = i === 0 ? nonWhitespaceColumnInFirstPart : SmartIndenter . findFirstNonWhitespaceCharacterAndColumn ( parts [ i ] . pos , parts [ i ] . end , sourceFile , options ) ; const newIndentation = nonWhitespaceCharacterAndColumn . column + delta ; if ( newIndentation > 0 ) { const indentationString = getIndentationString ( newIndentation , options ) ; recordReplace ( startLinePos , nonWhitespaceCharacterAndColumn . character , indentationString ) ; } else { recordDelete ( startLinePos , nonWhitespaceCharacterAndColumn . character ) ; } } } function trimTrailingWhitespacesForLines ( line1 , line2 , range ? ) { for ( let line = line1 ; line < line2 ; line ++ ) { const lineStartPosition = getStartPositionOfLine ( line , sourceFile ) ; const lineEndPosition = getEndLinePosition ( line , sourceFile ) ; if ( range && ( isComment ( range . kind ) || isStringOrRegularExpressionOrTemplateLiteral ( range . kind ) ) && range . pos <= lineEndPosition && range . end > lineEndPosition ) { continue ; } const whitespaceStart = getTrailingWhitespaceStartPosition ( lineStartPosition , lineEndPosition ) ; if ( whitespaceStart !== - 1 ) { Debug . assert ( whitespaceStart === lineStartPosition || ! isWhiteSpaceSingleLine ( sourceFile . text . charCodeAt ( whitespaceStart - 1 ) ) ) ; recordDelete ( whitespaceStart , lineEndPosition + 1 - whitespaceStart ) ; } } } param start param end function getTrailingWhitespaceStartPosition ( start , end ) { let pos = end ; while ( pos >= start && isWhiteSpaceSingleLine ( sourceFile . text . charCodeAt ( pos ) ) ) { pos -- ; } if ( pos !== end ) { return pos + 1 ; } return - 1 ; } /**
         * Trimming will be done for lines after the previous range.
         * Exclude comments as they had been previously processed.
         */ function trimTrailingWhitespacesForRemainingRange ( trivias ) { let startPos = previousRange ? previousRange . end : originalRange . pos ; for ( const trivia of trivias ) { if ( isComment ( trivia . kind ) ) { if ( startPos < trivia . pos ) { trimTrailingWitespacesForPositions ( startPos , trivia . pos - 1 , previousRange ) ; } startPos = trivia . end + 1 ; } } if ( startPos < originalRange . end ) { trimTrailingWitespacesForPositions ( startPos , originalRange . end , previousRange ) ; } } function trimTrailingWitespacesForPositions ( startPos , endPos , previousRange ) { const startLine = sourceFile . getLineAndCharacterOfPosition ( startPos ) . line ; const endLine = sourceFile . getLineAndCharacterOfPosition ( endPos ) . line ; trimTrailingWhitespacesForLines ( startLine , endLine + 1 , previousRange ) ; } function recordDelete ( start , len ) { if ( len ) { edits . push ( createTextChangeFromStartLength ( start , len , "" ) ) ; } } function recordReplace ( start , len , newText ) { if ( len || newText ) { edits . push ( createTextChangeFromStartLength ( start , len , newText ) ) ; } } function recordInsert ( start , text ) { if ( text ) { edits . push ( createTextChangeFromStartLength ( start , 0 , text ) ) ; } } function applyRuleEdits ( rule , previousRange , previousStartLine , currentRange , currentStartLine ) { const onLaterLine = currentStartLine !== previousStartLine ; switch ( rule . action ) { case RuleAction . StopProcessingSpaceActions : return LineAction . None ; case RuleAction . DeleteSpace : if ( previousRange . end !== currentRange . pos ) { recordDelete ( previousRange . end , currentRange . pos - previousRange . end ) ; return onLaterLine ? LineAction . LineRemoved : LineAction . None ; } break ; case RuleAction . DeleteToken : recordDelete ( previousRange . pos , previousRange . end - previousRange . pos ) ; break ; case RuleAction . InsertNewLine : if ( rule . flags !== RuleFlags . CanDeleteNewLines && previousStartLine !== currentStartLine ) { return LineAction . None ; } const lineDelta = currentStartLine - previousStartLine ; if ( lineDelta !== 1 ) { recordReplace ( previousRange . end , currentRange . pos - previousRange . end , getNewLineOrDefaultFromHost ( host , options ) ) ; return onLaterLine ? LineAction . None : LineAction . LineAdded ; } break ; case RuleAction . InsertSpace : if ( rule . flags !== RuleFlags . CanDeleteNewLines && previousStartLine !== currentStartLine ) { return LineAction . None ; } const posDelta = currentRange . pos - previousRange . end ; if ( posDelta !== 1 || sourceFile . text . charCodeAt ( previousRange . end ) !== CharacterCodes . space ) { recordReplace ( previousRange . end , currentRange . pos - previousRange . end , " " ) ; return onLaterLine ? LineAction . LineRemoved : LineAction . None ; } break ; case RuleAction . InsertTrailingSemicolon : recordInsert ( previousRange . end , ";" ) ; } return LineAction . None ; } } const enum LineAction { None , LineAdded , LineRemoved } param precedingToken export function getRangeOfEnclosingComment ( sourceFile , position , precedingToken ? , tokenAtPosition = getTokenAtPosition ( sourceFile , position ) ) { const jsdoc = findAncestor ( tokenAtPosition , isJSDoc ) ; if ( jsdoc ) tokenAtPosition = jsdoc . parent ; const tokenStart = tokenAtPosition . getStart ( sourceFile ) ; if ( tokenStart <= position && position < tokenAtPosition . getEnd ( ) ) { return undefined ; } precedingToken = precedingToken === null ? undefined : precedingToken === undefined ? findPrecedingToken ( position , sourceFile ) : precedingToken ; const trailingRangesOfPreviousToken = precedingToken && getTrailingCommentRanges ( sourceFile . text , precedingToken . end ) ; const leadingCommentRangesOfNextToken = getLeadingCommentRangesOfNode ( tokenAtPosition , sourceFile ) ; const commentRanges = concatenate ( trailingRangesOfPreviousToken , leadingCommentRangesOfNextToken ) ; return commentRanges && find ( commentRanges , range => rangeContainsPositionExclusive ( range , position ) || position === range . end && ( range . kind === SyntaxKind . SingleLineCommentTrivia || position === sourceFile . getFullWidth ( ) ) ) ; } function getOpenTokenForList ( node , list ) { switch ( node . kind ) { case SyntaxKind . Constructor : case SyntaxKind . FunctionDeclaration : case SyntaxKind . FunctionExpression : case SyntaxKind . MethodDeclaration : case SyntaxKind . MethodSignature : case SyntaxKind . ArrowFunction : if ( ( node as FunctionDeclaration ) . typeParameters === list ) { return SyntaxKind . LessThanToken ; } else if ( ( node as FunctionDeclaration ) . parameters === list ) { return SyntaxKind . OpenParenToken ; } break ; case SyntaxKind . CallExpression : case SyntaxKind . NewExpression : if ( ( node as CallExpression ) . typeArguments === list ) { return SyntaxKind . LessThanToken ; } else if ( ( node as CallExpression ) . arguments === list ) { return SyntaxKind . OpenParenToken ; } break ; case SyntaxKind . TypeReference : if ( ( node as TypeReferenceNode ) . typeArguments === list ) { return SyntaxKind . LessThanToken ; } break ; case SyntaxKind . TypeLiteral : return SyntaxKind . OpenBraceToken ; } return SyntaxKind . Unknown ; } function getCloseTokenForOpenToken ( kind ) { switch ( kind ) { case SyntaxKind . OpenParenToken : return SyntaxKind . CloseParenToken ; case SyntaxKind . LessThanToken : return SyntaxKind . GreaterThanToken ; case SyntaxKind . OpenBraceToken : return SyntaxKind . CloseBraceToken ; } return SyntaxKind . Unknown ; } let internedSizes ; let internedTabsIndentation ; let internedSpacesIndentation ; export function getIndentationString ( indentation , options ) { const resetInternedStrings = ! internedSizes || ( internedSizes . tabSize !== options . tabSize || internedSizes . indentSize !== options . indentSize ) ; if ( resetInternedStrings ) { internedSizes = { tabSize : options . tabSize ! , indentSize : options . indentSize ! } ; internedTabsIndentation = internedSpacesIndentation = undefined ; } if ( ! options . convertTabsToSpaces ) { const tabs = Math . floor ( indentation / options . tabSize ! ) ; const spaces = indentation - tabs * options . tabSize ! ; let tabString ; if ( ! internedTabsIndentation ) { internedTabsIndentation = [ ] ; } if ( internedTabsIndentation [ tabs ] === undefined ) { internedTabsIndentation [ tabs ] = tabString = repeatString ( "\t" , tabs ) ; } else { tabString = internedTabsIndentation [ tabs ] ; } return spaces ? tabString + repeatString ( " " , spaces ) : tabString ; } else { let spacesString ; const quotient = Math . floor ( indentation / options . indentSize ! ) ; const remainder = indentation % options . indentSize ! ; if ( ! internedSpacesIndentation ) { internedSpacesIndentation = [ ] ; } if ( internedSpacesIndentation [ quotient ] === undefined ) { spacesString = repeatString ( " " , options . indentSize ! * quotient ) ; internedSpacesIndentation [ quotient ] = spacesString ; } else { spacesString = internedSpacesIndentation [ quotient ] ; } return remainder ? spacesString + repeatString ( " " , remainder ) : spacesString ; } } }