namespace ts . OutliningElementsCollector { export function collectElements ( sourceFile , cancellationToken ) { const res = [ ] ; addNodeOutliningSpans ( sourceFile , cancellationToken , res ) ; addRegionOutliningSpans ( sourceFile , res ) ; return res . sort ( ( span1 , span2 ) => span1 . textSpan . start - span2 . textSpan . start ) ; } function addNodeOutliningSpans ( sourceFile , cancellationToken , out ) { let depthRemaining = 40 ; let current = 0 ; const statements = [ ... sourceFile . statements , sourceFile . endOfFileToken ] ; const n = statements . length ; while ( current < n ) { while ( current < n && ! isAnyImportSyntax ( statements [ current ] ) ) { visitNonImportNode ( statements [ current ] ) ; current ++ ; } if ( current === n ) break ; const firstImport = current ; while ( current < n && isAnyImportSyntax ( statements [ current ] ) ) { addOutliningForLeadingCommentsForNode ( statements [ current ] , sourceFile , cancellationToken , out ) ; current ++ ; } const lastImport = current - 1 ; if ( lastImport !== firstImport ) { out . push ( createOutliningSpanFromBounds ( findChildOfKind ( statements [ firstImport ] , SyntaxKind . ImportKeyword , sourceFile ) ! . getStart ( sourceFile ) , statements [ lastImport ] . getEnd ( ) , OutliningSpanKind . Imports ) ) ; } } function visitNonImportNode ( n ) { if ( depthRemaining === 0 ) return ; cancellationToken . throwIfCancellationRequested ( ) ; if ( isDeclaration ( n ) || isVariableStatement ( n ) || isReturnStatement ( n ) || isCallOrNewExpression ( n ) || n . kind === SyntaxKind . EndOfFileToken ) { addOutliningForLeadingCommentsForNode ( n , sourceFile , cancellationToken , out ) ; } if ( isFunctionLike ( n ) && isBinaryExpression ( n . parent ) && isPropertyAccessExpression ( n . parent . left ) ) { addOutliningForLeadingCommentsForNode ( n . parent . left , sourceFile , cancellationToken , out ) ; } if ( isBlock ( n ) || isModuleBlock ( n ) ) { addOutliningForLeadingCommentsForPos ( n . statements . end , sourceFile , cancellationToken , out ) ; } if ( isClassLike ( n ) || isInterfaceDeclaration ( n ) ) { addOutliningForLeadingCommentsForPos ( n . members . end , sourceFile , cancellationToken , out ) ; } const span = getOutliningSpanForNode ( n , sourceFile ) ; if ( span ) out . push ( span ) ; depthRemaining -- ; if ( isCallExpression ( n ) ) { depthRemaining ++ ; visitNonImportNode ( n . expression ) ; depthRemaining -- ; n . arguments . forEach ( visitNonImportNode ) ; n . typeArguments ?. forEach ( visitNonImportNode ) ; } else if ( isIfStatement ( n ) && n . elseStatement && isIfStatement ( n . elseStatement ) ) { visitNonImportNode ( n . expression ) ; visitNonImportNode ( n . thenStatement ) ; depthRemaining ++ ; visitNonImportNode ( n . elseStatement ) ; depthRemaining -- ; } else { n . forEachChild ( visitNonImportNode ) ; } depthRemaining ++ ; } } function addRegionOutliningSpans ( sourceFile , out ) { const regions = [ ] ; const lineStarts = sourceFile . getLineStarts ( ) ; for ( const currentLineStart of lineStarts ) { const lineEnd = sourceFile . getLineEndOfPosition ( currentLineStart ) ; const lineText = sourceFile . text . substring ( currentLineStart , lineEnd ) ; const result = isRegionDelimiter ( lineText ) ; if ( ! result || isInComment ( sourceFile , currentLineStart ) ) { continue ; } if ( ! result [ 1 ] ) { const span = createTextSpanFromBounds ( sourceFile . text . indexOf ( "//" , currentLineStart ) , lineEnd ) ; regions . push ( createOutliningSpan ( span , OutliningSpanKind . Region , span , false , result [ 2 ] || "#region" ) ) ; } else { const region = regions . pop ( ) ; if ( region ) { region . textSpan . length = lineEnd - region . textSpan . start ; region . hintSpan . length = lineEnd - region . textSpan . start ; out . push ( region ) ; } } } } const regionDelimiterRegExp = /^#(end)?region(?:\s+(.*))?(?:\r)?$/ ; function isRegionDelimiter ( lineText ) { lineText = trimStringStart ( lineText ) ; if ( ! startsWith ( lineText , "\/\/" ) ) { return null ; } lineText = trimString ( lineText . slice ( 2 ) ) ; return regionDelimiterRegExp . exec ( lineText ) ; } function addOutliningForLeadingCommentsForPos ( pos , sourceFile , cancellationToken , out ) { const comments = getLeadingCommentRanges ( sourceFile . text , pos ) ; if ( ! comments ) return ; let firstSingleLineCommentStart = - 1 ; let lastSingleLineCommentEnd = - 1 ; let singleLineCommentCount = 0 ; const sourceText = sourceFile . getFullText ( ) ; for ( const { kind , pos , end } of comments ) { cancellationToken . throwIfCancellationRequested ( ) ; switch ( kind ) { case SyntaxKind . SingleLineCommentTrivia : const commentText = sourceText . slice ( pos , end ) ; if ( isRegionDelimiter ( commentText ) ) { combineAndAddMultipleSingleLineComments ( ) ; singleLineCommentCount = 0 ; break ; } if ( singleLineCommentCount === 0 ) { firstSingleLineCommentStart = pos ; } lastSingleLineCommentEnd = end ; singleLineCommentCount ++ ; break ; case SyntaxKind . MultiLineCommentTrivia : combineAndAddMultipleSingleLineComments ( ) ; out . push ( createOutliningSpanFromBounds ( pos , end , OutliningSpanKind . Comment ) ) ; singleLineCommentCount = 0 ; break ; default : Debug . assertNever ( kind ) ; } } combineAndAddMultipleSingleLineComments ( ) ; function combineAndAddMultipleSingleLineComments ( ) { if ( singleLineCommentCount > 1 ) { out . push ( createOutliningSpanFromBounds ( firstSingleLineCommentStart , lastSingleLineCommentEnd , OutliningSpanKind . Comment ) ) ; } } } function addOutliningForLeadingCommentsForNode ( n , sourceFile , cancellationToken , out ) { if ( isJsxText ( n ) ) return ; addOutliningForLeadingCommentsForPos ( n . pos , sourceFile , cancellationToken , out ) ; } function createOutliningSpanFromBounds ( pos , end , kind ) { return createOutliningSpan ( createTextSpanFromBounds ( pos , end ) , kind ) ; } function getOutliningSpanForNode ( n , sourceFile ) { switch ( n . kind ) { case SyntaxKind . Block : if ( isFunctionLike ( n . parent ) ) { return functionSpan ( n . parent , n as Block , sourceFile ) ; } switch ( n . parent . kind ) { case SyntaxKind . DoStatement : case SyntaxKind . ForInStatement : case SyntaxKind . ForOfStatement : case SyntaxKind . ForStatement : case SyntaxKind . IfStatement : case SyntaxKind . WhileStatement : case SyntaxKind . WithStatement : case SyntaxKind . CatchClause : return spanForNode ( n . parent ) ; case SyntaxKind . TryStatement : const tryStatement = n . parent as TryStatement ; if ( tryStatement . tryBlock === n ) { return spanForNode ( n . parent ) ; } else if ( tryStatement . finallyBlock === n ) { const node = findChildOfKind ( tryStatement , SyntaxKind . FinallyKeyword , sourceFile ) ; if ( node ) return spanForNode ( node ) ; } default : return createOutliningSpan ( createTextSpanFromNode ( n , sourceFile ) , OutliningSpanKind . Code ) ; } case SyntaxKind . ModuleBlock : return spanForNode ( n . parent ) ; case SyntaxKind . ClassDeclaration : case SyntaxKind . ClassExpression : case SyntaxKind . InterfaceDeclaration : case SyntaxKind . EnumDeclaration : case SyntaxKind . CaseBlock : case SyntaxKind . TypeLiteral : case SyntaxKind . ObjectBindingPattern : return spanForNode ( n ) ; case SyntaxKind . TupleType : return spanForNode ( n , false , ! isTupleTypeNode ( n . parent ) , SyntaxKind . OpenBracketToken ) ; case SyntaxKind . CaseClause : case SyntaxKind . DefaultClause : return spanForNodeArray ( ( n as CaseClause | DefaultClause ) . statements ) ; case SyntaxKind . ObjectLiteralExpression : return spanForObjectOrArrayLiteral ( n ) ; case SyntaxKind . ArrayLiteralExpression : return spanForObjectOrArrayLiteral ( n , SyntaxKind . OpenBracketToken ) ; case SyntaxKind . JsxElement : return spanForJSXElement ( n as JsxElement ) ; case SyntaxKind . JsxFragment : return spanForJSXFragment ( n as JsxFragment ) ; case SyntaxKind . JsxSelfClosingElement : case SyntaxKind . JsxOpeningElement : return spanForJSXAttributes ( ( n as JsxOpeningLikeElement ) . attributes ) ; case SyntaxKind . TemplateExpression : case SyntaxKind . NoSubstitutionTemplateLiteral : return spanForTemplateLiteral ( n as TemplateExpression | NoSubstitutionTemplateLiteral ) ; case SyntaxKind . ArrayBindingPattern : return spanForNode ( n , false , ! isBindingElement ( n . parent ) , SyntaxKind . OpenBracketToken ) ; case SyntaxKind . ArrowFunction : return spanForArrowFunction ( n as ArrowFunction ) ; case SyntaxKind . CallExpression : return spanForCallExpression ( n as CallExpression ) ; } function spanForCallExpression ( node ) { if ( ! node . arguments . length ) { return undefined ; } const openToken = findChildOfKind ( node , SyntaxKind . OpenParenToken , sourceFile ) ; const closeToken = findChildOfKind ( node , SyntaxKind . CloseParenToken , sourceFile ) ; if ( ! openToken || ! closeToken || positionsAreOnSameLine ( openToken . pos , closeToken . pos , sourceFile ) ) { return undefined ; } return spanBetweenTokens ( openToken , closeToken , node , sourceFile , false , true ) ; } function spanForArrowFunction ( node ) { if ( isBlock ( node . body ) || positionsAreOnSameLine ( node . body . getFullStart ( ) , node . body . getEnd ( ) , sourceFile ) ) { return undefined ; } const textSpan = createTextSpanFromBounds ( node . body . getFullStart ( ) , node . body . getEnd ( ) ) ; return createOutliningSpan ( textSpan , OutliningSpanKind . Code , createTextSpanFromNode ( node ) ) ; } function spanForJSXElement ( node ) { const textSpan = createTextSpanFromBounds ( node . openingElement . getStart ( sourceFile ) , node . closingElement . getEnd ( ) ) ; const tagName = node . openingElement . tagName . getText ( sourceFile ) ; const bannerText = "<" + tagName + ">...</" + tagName + ">" ; return createOutliningSpan ( textSpan , OutliningSpanKind . Code , textSpan , false , bannerText ) ; } function spanForJSXFragment ( node ) { const textSpan = createTextSpanFromBounds ( node . openingFragment . getStart ( sourceFile ) , node . closingFragment . getEnd ( ) ) ; const bannerText = "<>...</>" ; return createOutliningSpan ( textSpan , OutliningSpanKind . Code , textSpan , false , bannerText ) ; } function spanForJSXAttributes ( node ) { if ( node . properties . length === 0 ) { return undefined ; } return createOutliningSpanFromBounds ( node . getStart ( sourceFile ) , node . getEnd ( ) , OutliningSpanKind . Code ) ; } function spanForTemplateLiteral ( node ) { if ( node . kind === SyntaxKind . NoSubstitutionTemplateLiteral && node . text . length === 0 ) { return undefined ; } return createOutliningSpanFromBounds ( node . getStart ( sourceFile ) , node . getEnd ( ) , OutliningSpanKind . Code ) ; } function spanForObjectOrArrayLiteral ( node , open = SyntaxKind . OpenBraceToken ) { return spanForNode ( node , false , ! isArrayLiteralExpression ( node . parent ) && ! isCallExpression ( node . parent ) , open ) ; } function spanForNode ( hintSpanNode , autoCollapse = false , useFullStart = true , open = SyntaxKind . OpenBraceToken , close = open === SyntaxKind . OpenBraceToken ? SyntaxKind . CloseBraceToken : SyntaxKind . CloseBracketToken ) { const openToken = findChildOfKind ( n , open , sourceFile ) ; const closeToken = findChildOfKind ( n , close , sourceFile ) ; return openToken && closeToken && spanBetweenTokens ( openToken , closeToken , hintSpanNode , sourceFile , autoCollapse , useFullStart ) ; } function spanForNodeArray ( nodeArray ) { return nodeArray . length ? createOutliningSpan ( createTextSpanFromRange ( nodeArray ) , OutliningSpanKind . Code ) : undefined ; } } function functionSpan ( node , body , sourceFile ) { const openToken = tryGetFunctionOpenToken ( node , body , sourceFile ) ; const closeToken = findChildOfKind ( body , SyntaxKind . CloseBraceToken , sourceFile ) ; return openToken && closeToken && spanBetweenTokens ( openToken , closeToken , node , sourceFile , node . kind !== SyntaxKind . ArrowFunction ) ; } function spanBetweenTokens ( openToken , closeToken , hintSpanNode , sourceFile , autoCollapse = false , useFullStart = true ) { const textSpan = createTextSpanFromBounds ( useFullStart ? openToken . getFullStart ( ) : openToken . getStart ( sourceFile ) , closeToken . getEnd ( ) ) ; return createOutliningSpan ( textSpan , OutliningSpanKind . Code , createTextSpanFromNode ( hintSpanNode , sourceFile ) , autoCollapse ) ; } function createOutliningSpan ( textSpan , kind , hintSpan = textSpan , autoCollapse = false , bannerText = "..." ) { return { textSpan , kind , hintSpan , bannerText , autoCollapse } ; } function tryGetFunctionOpenToken ( node , body , sourceFile ) { if ( isNodeArrayMultiLine ( node . parameters , sourceFile ) ) { const openParenToken = findChildOfKind ( node , SyntaxKind . OpenParenToken , sourceFile ) ; if ( openParenToken ) { return openParenToken ; } } return findChildOfKind ( body , SyntaxKind . OpenBraceToken , sourceFile ) ; } }