namespace ts . formatting { export namespace SmartIndenter { const enum Value { Unknown = - 1 } param assumeNewLineBeforeCloseBrace export function getIndentation ( position , sourceFile , options , assumeNewLineBeforeCloseBrace = false ) { if ( position > sourceFile . text . length ) { return getBaseIndentation ( options ) ; } if ( options . indentStyle === IndentStyle . None ) { return 0 ; } const precedingToken = findPrecedingToken ( position , sourceFile , undefined , true ) ; const enclosingCommentRange = getRangeOfEnclosingComment ( sourceFile , position , precedingToken || null ) ; if ( enclosingCommentRange && enclosingCommentRange . kind === SyntaxKind . MultiLineCommentTrivia ) { return getCommentIndent ( sourceFile , position , options , enclosingCommentRange ) ; } if ( ! precedingToken ) { return getBaseIndentation ( options ) ; } const precedingTokenIsLiteral = isStringOrRegularExpressionOrTemplateLiteral ( precedingToken . kind ) ; if ( precedingTokenIsLiteral && precedingToken . getStart ( sourceFile ) <= position && position < precedingToken . end ) { return 0 ; } const lineAtPosition = sourceFile . getLineAndCharacterOfPosition ( position ) . line ; if ( options . indentStyle === IndentStyle . Block ) { return getBlockIndent ( sourceFile , position , options ) ; } if ( precedingToken . kind === SyntaxKind . CommaToken && precedingToken . parent . kind !== SyntaxKind . BinaryExpression ) { const actualIndentation = getActualIndentationForListItemBeforeComma ( precedingToken , sourceFile , options ) ; if ( actualIndentation !== Value . Unknown ) { return actualIndentation ; } } const containerList = getListByPosition ( position , precedingToken . parent , sourceFile ) ; if ( containerList && ! rangeContainsRange ( containerList , precedingToken ) ) { return getActualIndentationForListStartLine ( containerList , sourceFile , options ) + options . indentSize ! ; } return getSmartIndent ( sourceFile , position , precedingToken , lineAtPosition , assumeNewLineBeforeCloseBrace , options ) ; } function getCommentIndent ( sourceFile , position , options , enclosingCommentRange ) { const previousLine = getLineAndCharacterOfPosition ( sourceFile , position ) . line - 1 ; const commentStartLine = getLineAndCharacterOfPosition ( sourceFile , enclosingCommentRange . pos ) . line ; Debug . assert ( commentStartLine >= 0 ) ; if ( previousLine <= commentStartLine ) { return findFirstNonWhitespaceColumn ( getStartPositionOfLine ( commentStartLine , sourceFile ) , position , sourceFile , options ) ; } const startPositionOfLine = getStartPositionOfLine ( previousLine , sourceFile ) ; const { column , character } = findFirstNonWhitespaceCharacterAndColumn ( startPositionOfLine , position , sourceFile , options ) ; if ( column === 0 ) { return column ; } const firstNonWhitespaceCharacterCode = sourceFile . text . charCodeAt ( startPositionOfLine + character ) ; return firstNonWhitespaceCharacterCode === CharacterCodes . asterisk ? column - 1 : column ; } function getBlockIndent ( sourceFile , position , options ) { let current = position ; while ( current > 0 ) { const char = sourceFile . text . charCodeAt ( current ) ; if ( ! isWhiteSpaceLike ( char ) ) { break ; } current -- ; } const lineStart = getLineStartPositionForPosition ( current , sourceFile ) ; return findFirstNonWhitespaceColumn ( lineStart , current , sourceFile , options ) ; } function getSmartIndent ( sourceFile , position , precedingToken , lineAtPosition , assumeNewLineBeforeCloseBrace , options ) { let previous ; let current = precedingToken ; while ( current ) { if ( positionBelongsToNode ( current , position , sourceFile ) && shouldIndentChildNode ( options , current , previous , sourceFile , true ) ) { const currentStart = getStartLineAndCharacterForNode ( current , sourceFile ) ; const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor ( precedingToken , current , lineAtPosition , sourceFile ) ; const indentationDelta = nextTokenKind !== NextTokenKind . Unknown ? assumeNewLineBeforeCloseBrace && nextTokenKind === NextTokenKind . CloseBrace ? options . indentSize : 0 : lineAtPosition !== currentStart . line ? options . indentSize : 0 ; return getIndentationForNodeWorker ( current , currentStart , undefined , indentationDelta ! , sourceFile , true , options ) ; } const actualIndentation = getActualIndentationForListItem ( current , sourceFile , options , true ) ; if ( actualIndentation !== Value . Unknown ) { return actualIndentation ; } previous = current ; current = current . parent ; } return getBaseIndentation ( options ) ; } export function getIndentationForNode ( n , ignoreActualIndentationRange , sourceFile , options ) { const start = sourceFile . getLineAndCharacterOfPosition ( n . getStart ( sourceFile ) ) ; return getIndentationForNodeWorker ( n , start , ignoreActualIndentationRange , 0 , sourceFile , false , options ) ; } export function getBaseIndentation ( options ) { return options . baseIndentSize || 0 ; } function getIndentationForNodeWorker ( current , currentStart , ignoreActualIndentationRange , indentationDelta , sourceFile , isNextChild , options ) { let parent = current . parent ; while ( parent ) { let useActualIndentation = true ; if ( ignoreActualIndentationRange ) { const start = current . getStart ( sourceFile ) ; useActualIndentation = start < ignoreActualIndentationRange . pos || start > ignoreActualIndentationRange . end ; } const containingListOrParentStart = getContainingListOrParentStart ( parent , current , sourceFile ) ; const parentAndChildShareLine = containingListOrParentStart . line === currentStart . line || childStartsOnTheSameLineWithElseInIfStatement ( parent , current , currentStart . line , sourceFile ) ; if ( useActualIndentation ) { const firstListChild = getContainingList ( current , sourceFile ) ?. [ 0 ] ; const listIndentsChild = ! ! firstListChild && getStartLineAndCharacterForNode ( firstListChild , sourceFile ) . line > containingListOrParentStart . line ; let actualIndentation = getActualIndentationForListItem ( current , sourceFile , options , listIndentsChild ) ; if ( actualIndentation !== Value . Unknown ) { return actualIndentation + indentationDelta ; } actualIndentation = getActualIndentationForNode ( current , parent , currentStart , parentAndChildShareLine , sourceFile , options ) ; if ( actualIndentation !== Value . Unknown ) { return actualIndentation + indentationDelta ; } } if ( shouldIndentChildNode ( options , parent , current , sourceFile , isNextChild ) && ! parentAndChildShareLine ) { indentationDelta += options . indentSize ! ; } const useTrueStart = isArgumentAndStartLineOverlapsExpressionBeingCalled ( parent , current , currentStart . line , sourceFile ) ; current = parent ; parent = current . parent ; currentStart = useTrueStart ? sourceFile . getLineAndCharacterOfPosition ( current . getStart ( sourceFile ) ) : containingListOrParentStart ; } return indentationDelta + getBaseIndentation ( options ) ; } function getContainingListOrParentStart ( parent , child , sourceFile ) { const containingList = getContainingList ( child , sourceFile ) ; const startPos = containingList ? containingList . pos : parent . getStart ( sourceFile ) ; return sourceFile . getLineAndCharacterOfPosition ( startPos ) ; } function getActualIndentationForListItemBeforeComma ( commaToken , sourceFile , options ) { const commaItemInfo = findListItemInfo ( commaToken ) ; if ( commaItemInfo && commaItemInfo . listItemIndex > 0 ) { return deriveActualIndentationFromList ( commaItemInfo . list . getChildren ( ) , commaItemInfo . listItemIndex - 1 , sourceFile , options ) ; } else { return Value . Unknown ; } } function getActualIndentationForNode ( current , parent , currentLineAndChar , parentAndChildShareLine , sourceFile , options ) { const useActualIndentation = ( isDeclaration ( current ) || isStatementButNotDeclaration ( current ) ) && ( parent . kind === SyntaxKind . SourceFile || ! parentAndChildShareLine ) ; if ( ! useActualIndentation ) { return Value . Unknown ; } return findColumnForFirstNonWhitespaceCharacterInLine ( currentLineAndChar , sourceFile , options ) ; } const enum NextTokenKind { Unknown , OpenBrace , CloseBrace } function nextTokenIsCurlyBraceOnSameLineAsCursor ( precedingToken , current , lineAtPosition , sourceFile ) { const nextToken = findNextToken ( precedingToken , current , sourceFile ) ; if ( ! nextToken ) { return NextTokenKind . Unknown ; } if ( nextToken . kind === SyntaxKind . OpenBraceToken ) { return NextTokenKind . OpenBrace ; } else if ( nextToken . kind === SyntaxKind . CloseBraceToken ) { const nextTokenStartLine = getStartLineAndCharacterForNode ( nextToken , sourceFile ) . line ; return lineAtPosition === nextTokenStartLine ? NextTokenKind . CloseBrace : NextTokenKind . Unknown ; } return NextTokenKind . Unknown ; } function getStartLineAndCharacterForNode ( n , sourceFile ) { return sourceFile . getLineAndCharacterOfPosition ( n . getStart ( sourceFile ) ) ; } export function isArgumentAndStartLineOverlapsExpressionBeingCalled ( parent , child , childStartLine , sourceFile ) { if ( ! ( isCallExpression ( parent ) && contains ( parent . arguments , child ) ) ) { return false ; } const expressionOfCallExpressionEnd = parent . expression . getEnd ( ) ; const expressionOfCallExpressionEndLine = getLineAndCharacterOfPosition ( sourceFile , expressionOfCallExpressionEnd ) . line ; return expressionOfCallExpressionEndLine === childStartLine ; } export function childStartsOnTheSameLineWithElseInIfStatement ( parent , child , childStartLine , sourceFile ) { if ( parent . kind === SyntaxKind . IfStatement && ( parent as IfStatement ) . elseStatement === child ) { const elseKeyword = findChildOfKind ( parent , SyntaxKind . ElseKeyword , sourceFile ) ! ; Debug . assert ( elseKeyword !== undefined ) ; const elseKeywordStartLine = getStartLineAndCharacterForNode ( elseKeyword , sourceFile ) . line ; return elseKeywordStartLine === childStartLine ; } return false ; } export function childIsUnindentedBranchOfConditionalExpression ( parent , child , childStartLine , sourceFile ) { if ( isConditionalExpression ( parent ) && ( child === parent . whenTrue || child === parent . whenFalse ) ) { const conditionEndLine = getLineAndCharacterOfPosition ( sourceFile , parent . condition . end ) . line ; if ( child === parent . whenTrue ) { return childStartLine === conditionEndLine ; } else { const trueStartLine = getStartLineAndCharacterForNode ( parent . whenTrue , sourceFile ) . line ; const trueEndLine = getLineAndCharacterOfPosition ( sourceFile , parent . whenTrue . end ) . line ; return conditionEndLine === trueStartLine && trueEndLine === childStartLine ; } } return false ; } export function argumentStartsOnSameLineAsPreviousArgument ( parent , child , childStartLine , sourceFile ) { if ( isCallOrNewExpression ( parent ) ) { if ( ! parent . arguments ) return false ; const currentNode = find ( parent . arguments , arg => arg . pos === child . pos ) ; if ( ! currentNode ) return false ; const currentIndex = parent . arguments . indexOf ( currentNode ) ; if ( currentIndex === 0 ) return false ; const previousNode = parent . arguments [ currentIndex - 1 ] ; const lineOfPreviousNode = getLineAndCharacterOfPosition ( sourceFile , previousNode . getEnd ( ) ) . line ; if ( childStartLine === lineOfPreviousNode ) { return true ; } } return false ; } export function getContainingList ( node , sourceFile ) { return node . parent && getListByRange ( node . getStart ( sourceFile ) , node . getEnd ( ) , node . parent , sourceFile ) ; } function getListByPosition ( pos , node , sourceFile ) { return node && getListByRange ( pos , pos , node , sourceFile ) ; } function getListByRange ( start , end , node , sourceFile ) { switch ( node . kind ) { case SyntaxKind . TypeReference : return getList ( ( node as TypeReferenceNode ) . typeArguments ) ; case SyntaxKind . ObjectLiteralExpression : return getList ( ( node as ObjectLiteralExpression ) . properties ) ; case SyntaxKind . ArrayLiteralExpression : return getList ( ( node as ArrayLiteralExpression ) . elements ) ; case SyntaxKind . TypeLiteral : return getList ( ( node as TypeLiteralNode ) . members ) ; case SyntaxKind . FunctionDeclaration : case SyntaxKind . FunctionExpression : case SyntaxKind . ArrowFunction : case SyntaxKind . MethodDeclaration : case SyntaxKind . MethodSignature : case SyntaxKind . CallSignature : case SyntaxKind . Constructor : case SyntaxKind . ConstructorType : case SyntaxKind . ConstructSignature : return getList ( ( node as SignatureDeclaration ) . typeParameters ) || getList ( ( node as SignatureDeclaration ) . parameters ) ; case SyntaxKind . ClassDeclaration : case SyntaxKind . ClassExpression : case SyntaxKind . InterfaceDeclaration : case SyntaxKind . TypeAliasDeclaration : case SyntaxKind . JSDocTemplateTag : return getList ( ( node as ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag ) . typeParameters ) ; case SyntaxKind . NewExpression : case SyntaxKind . CallExpression : return getList ( ( node as CallExpression ) . typeArguments ) || getList ( ( node as CallExpression ) . arguments ) ; case SyntaxKind . VariableDeclarationList : return getList ( ( node as VariableDeclarationList ) . declarations ) ; case SyntaxKind . NamedImports : case SyntaxKind . NamedExports : return getList ( ( node as NamedImportsOrExports ) . elements ) ; case SyntaxKind . ObjectBindingPattern : case SyntaxKind . ArrayBindingPattern : return getList ( ( node as ObjectBindingPattern | ArrayBindingPattern ) . elements ) ; } function getList ( list ) { return list && rangeContainsStartEnd ( getVisualListRange ( node , list , sourceFile ) , start , end ) ? list : undefined ; } } function getVisualListRange ( node , list , sourceFile ) { const children = node . getChildren ( sourceFile ) ; for ( let i = 1 ; i < children . length - 1 ; i ++ ) { if ( children [ i ] . pos === list . pos && children [ i ] . end === list . end ) { return { pos : children [ i - 1 ] . end , end : children [ i + 1 ] . getStart ( sourceFile ) } ; } } return list ; } function getActualIndentationForListStartLine ( list , sourceFile , options ) { if ( ! list ) { return Value . Unknown ; } return findColumnForFirstNonWhitespaceCharacterInLine ( sourceFile . getLineAndCharacterOfPosition ( list . pos ) , sourceFile , options ) ; } function getActualIndentationForListItem ( node , sourceFile , options , listIndentsChild ) { if ( node . parent && node . parent . kind === SyntaxKind . VariableDeclarationList ) { return Value . Unknown ; } const containingList = getContainingList ( node , sourceFile ) ; if ( containingList ) { const index = containingList . indexOf ( node ) ; if ( index !== - 1 ) { const result = deriveActualIndentationFromList ( containingList , index , sourceFile , options ) ; if ( result !== Value . Unknown ) { return result ; } } return getActualIndentationForListStartLine ( containingList , sourceFile , options ) + ( listIndentsChild ? options . indentSize ! : 0 ) ; } return Value . Unknown ; } function deriveActualIndentationFromList ( list , index , sourceFile , options ) { Debug . assert ( index >= 0 && index < list . length ) ; const node = list [ index ] ; let lineAndCharacter = getStartLineAndCharacterForNode ( node , sourceFile ) ; for ( let i = index - 1 ; i >= 0 ; i -- ) { if ( list [ i ] . kind === SyntaxKind . CommaToken ) { continue ; } const prevEndLine = sourceFile . getLineAndCharacterOfPosition ( list [ i ] . end ) . line ; if ( prevEndLine !== lineAndCharacter . line ) { return findColumnForFirstNonWhitespaceCharacterInLine ( lineAndCharacter , sourceFile , options ) ; } lineAndCharacter = getStartLineAndCharacterForNode ( list [ i ] , sourceFile ) ; } return Value . Unknown ; } function findColumnForFirstNonWhitespaceCharacterInLine ( lineAndCharacter , sourceFile , options ) { const lineStart = sourceFile . getPositionOfLineAndCharacter ( lineAndCharacter . line , 0 ) ; return findFirstNonWhitespaceColumn ( lineStart , lineStart + lineAndCharacter . character , sourceFile , options ) ; } /**
         * Character is the actual index of the character since the beginning of the line.
         * Column - position of the character after expanding tabs to spaces.
         * "0\t2$"
         * value of 'character' for '$' is 3
         * value of 'column' for '$' is 6 (assuming that tab size is 4)
         */ export function findFirstNonWhitespaceCharacterAndColumn ( startPos , endPos , sourceFile , options ) { let character = 0 ; let column = 0 ; for ( let pos = startPos ; pos < endPos ; pos ++ ) { const ch = sourceFile . text . charCodeAt ( pos ) ; if ( ! isWhiteSpaceSingleLine ( ch ) ) { break ; } if ( ch === CharacterCodes . tab ) { column += options . tabSize ! + ( column % options . tabSize ! ) ; } else { column ++ ; } character ++ ; } return { column , character } ; } export function findFirstNonWhitespaceColumn ( startPos , endPos , sourceFile , options ) { return findFirstNonWhitespaceCharacterAndColumn ( startPos , endPos , sourceFile , options ) . column ; } export function nodeWillIndentChild ( settings , parent , child , sourceFile , indentByDefault ) { const childKind = child ? child . kind : SyntaxKind . Unknown ; switch ( parent . kind ) { case SyntaxKind . ExpressionStatement : case SyntaxKind . ClassDeclaration : case SyntaxKind . ClassExpression : case SyntaxKind . InterfaceDeclaration : case SyntaxKind . EnumDeclaration : case SyntaxKind . TypeAliasDeclaration : case SyntaxKind . ArrayLiteralExpression : case SyntaxKind . Block : case SyntaxKind . ModuleBlock : case SyntaxKind . ObjectLiteralExpression : case SyntaxKind . TypeLiteral : case SyntaxKind . MappedType : case SyntaxKind . TupleType : case SyntaxKind . CaseBlock : case SyntaxKind . DefaultClause : case SyntaxKind . CaseClause : case SyntaxKind . ParenthesizedExpression : case SyntaxKind . PropertyAccessExpression : case SyntaxKind . CallExpression : case SyntaxKind . NewExpression : case SyntaxKind . VariableStatement : case SyntaxKind . ExportAssignment : case SyntaxKind . ReturnStatement : case SyntaxKind . ConditionalExpression : case SyntaxKind . ArrayBindingPattern : case SyntaxKind . ObjectBindingPattern : case SyntaxKind . JsxOpeningElement : case SyntaxKind . JsxOpeningFragment : case SyntaxKind . JsxSelfClosingElement : case SyntaxKind . JsxExpression : case SyntaxKind . MethodSignature : case SyntaxKind . CallSignature : case SyntaxKind . ConstructSignature : case SyntaxKind . Parameter : case SyntaxKind . FunctionType : case SyntaxKind . ConstructorType : case SyntaxKind . ParenthesizedType : case SyntaxKind . TaggedTemplateExpression : case SyntaxKind . AwaitExpression : case SyntaxKind . NamedExports : case SyntaxKind . NamedImports : case SyntaxKind . ExportSpecifier : case SyntaxKind . ImportSpecifier : case SyntaxKind . PropertyDeclaration : return true ; case SyntaxKind . VariableDeclaration : case SyntaxKind . PropertyAssignment : case SyntaxKind . BinaryExpression : if ( ! settings . indentMultiLineObjectLiteralBeginningOnBlankLine && sourceFile && childKind === SyntaxKind . ObjectLiteralExpression ) { return rangeIsOnOneLine ( sourceFile , child ! ) ; } if ( parent . kind === SyntaxKind . BinaryExpression && sourceFile && child && childKind === SyntaxKind . JsxElement ) { const parentStartLine = sourceFile . getLineAndCharacterOfPosition ( skipTrivia ( sourceFile . text , parent . pos ) ) . line ; const childStartLine = sourceFile . getLineAndCharacterOfPosition ( skipTrivia ( sourceFile . text , child . pos ) ) . line ; return parentStartLine !== childStartLine ; } if ( parent . kind !== SyntaxKind . BinaryExpression ) { return true ; } break ; case SyntaxKind . DoStatement : case SyntaxKind . WhileStatement : case SyntaxKind . ForInStatement : case SyntaxKind . ForOfStatement : case SyntaxKind . ForStatement : case SyntaxKind . IfStatement : case SyntaxKind . FunctionDeclaration : case SyntaxKind . FunctionExpression : case SyntaxKind . MethodDeclaration : case SyntaxKind . Constructor : case SyntaxKind . GetAccessor : case SyntaxKind . SetAccessor : return childKind !== SyntaxKind . Block ; case SyntaxKind . ArrowFunction : if ( sourceFile && childKind === SyntaxKind . ParenthesizedExpression ) { return rangeIsOnOneLine ( sourceFile , child ! ) ; } return childKind !== SyntaxKind . Block ; case SyntaxKind . ExportDeclaration : return childKind !== SyntaxKind . NamedExports ; case SyntaxKind . ImportDeclaration : return childKind !== SyntaxKind . ImportClause || ( ! ! ( child as ImportClause ) . namedBindings && ( child as ImportClause ) . namedBindings ! . kind !== SyntaxKind . NamedImports ) ; case SyntaxKind . JsxElement : return childKind !== SyntaxKind . JsxClosingElement ; case SyntaxKind . JsxFragment : return childKind !== SyntaxKind . JsxClosingFragment ; case SyntaxKind . IntersectionType : case SyntaxKind . UnionType : if ( childKind === SyntaxKind . TypeLiteral || childKind === SyntaxKind . TupleType ) { return false ; } break ; } return indentByDefault ; } function isControlFlowEndingStatement ( kind , parent ) { switch ( kind ) { case SyntaxKind . ReturnStatement : case SyntaxKind . ThrowStatement : case SyntaxKind . ContinueStatement : case SyntaxKind . BreakStatement : return parent . kind !== SyntaxKind . Block ; default : return false ; } } param isNextChild export function shouldIndentChildNode ( settings , parent , child ? , sourceFile ? , isNextChild = false ) { return nodeWillIndentChild ( settings , parent , child , sourceFile , false ) && ! ( isNextChild && child && isControlFlowEndingStatement ( child . kind , parent ) ) ; } function rangeIsOnOneLine ( sourceFile , range ) { const rangeStart = skipTrivia ( sourceFile . text , range . pos ) ; const startLine = sourceFile . getLineAndCharacterOfPosition ( rangeStart ) . line ; const endLine = sourceFile . getLineAndCharacterOfPosition ( range . end ) . line ; return startLine === endLine ; } } }