11/* @internal */
22namespace ts . OutliningElementsCollector {
3- const collapseText = "..." ;
4- const maxDepth = 40 ;
5- const defaultLabel = "#region" ;
6- const regionMatch = new RegExp ( "^\\s*//\\s*#(end)?region(?:\\s+(.*))?$" ) ;
7-
83 export function collectElements ( sourceFile : SourceFile , cancellationToken : CancellationToken ) : OutliningSpan [ ] {
9- const elements : OutliningSpan [ ] = [ ] ;
10- let depth = 0 ;
11- const regions : OutliningSpan [ ] = [ ] ;
4+ const res : OutliningSpan [ ] = [ ] ;
5+ addNodeOutliningSpans ( sourceFile , cancellationToken , res ) ;
6+ addRegionOutliningSpans ( sourceFile , res ) ;
7+ return res . sort ( ( span1 , span2 ) => span1 . textSpan . start - span2 . textSpan . start ) ;
8+ }
129
13- walk ( sourceFile ) ;
14- gatherRegions ( ) ;
15- return elements . sort ( ( span1 , span2 ) => span1 . textSpan . start - span2 . textSpan . start ) ;
16-
17- /** If useFullStart is true, then the collapsing span includes leading whitespace, including linebreaks. */
18- function addOutliningSpan ( hintSpanNode : Node , startElement : Node , endElement : Node , autoCollapse : boolean , useFullStart : boolean ) {
19- if ( hintSpanNode && startElement && endElement ) {
20- const span : OutliningSpan = {
21- textSpan : createTextSpanFromBounds ( useFullStart ? startElement . getFullStart ( ) : startElement . getStart ( ) , endElement . getEnd ( ) ) ,
22- hintSpan : createTextSpanFromNode ( hintSpanNode , sourceFile ) ,
23- bannerText : collapseText ,
24- autoCollapse,
25- } ;
26- elements . push ( span ) ;
10+ function addNodeOutliningSpans ( sourceFile : SourceFile , cancellationToken : CancellationToken , out : Push < OutliningSpan > ) : void {
11+ let depth = 0 ;
12+ const maxDepth = 39 ;
13+ sourceFile . forEachChild ( function walk ( n ) {
14+ if ( depth > maxDepth ) {
15+ return ;
2716 }
28- }
17+ cancellationToken . throwIfCancellationRequested ( ) ;
2918
30- function addOutliningSpanComments ( commentSpan : CommentRange , autoCollapse : boolean ) {
31- if ( commentSpan ) {
32- const span : OutliningSpan = {
33- textSpan : createTextSpanFromBounds ( commentSpan . pos , commentSpan . end ) ,
34- hintSpan : createTextSpanFromBounds ( commentSpan . pos , commentSpan . end ) ,
35- bannerText : collapseText ,
36- autoCollapse,
37- } ;
38- elements . push ( span ) ;
19+ if ( isDeclaration ( n ) ) {
20+ addOutliningForLeadingCommentsForNode ( n , sourceFile , cancellationToken , out ) ;
3921 }
40- }
4122
42- function addOutliningForLeadingCommentsForNode ( n : Node ) {
43- const comments = ts . getLeadingCommentRangesOfNode ( n , sourceFile ) ;
23+ const span = getOutliningSpanForNode ( n , sourceFile ) ;
24+ if ( span ) out . push ( span ) ;
4425
45- if ( comments ) {
46- let firstSingleLineCommentStart = - 1 ;
47- let lastSingleLineCommentEnd = - 1 ;
48- let isFirstSingleLineComment = true ;
49- let singleLineCommentCount = 0 ;
26+ depth ++ ;
27+ n . forEachChild ( walk ) ;
28+ depth -- ;
29+ } ) ;
30+ }
5031
51- for ( const currentComment of comments ) {
52- cancellationToken . throwIfCancellationRequested ( ) ;
32+ function addRegionOutliningSpans ( sourceFile : SourceFile , out : Push < OutliningSpan > ) : void {
33+ const regions : OutliningSpan [ ] = [ ] ;
34+ const lineStarts = sourceFile . getLineStarts ( ) ;
35+ for ( let i = 0 ; i < lineStarts . length ; i ++ ) {
36+ const currentLineStart = lineStarts [ i ] ;
37+ const lineEnd = i + 1 === lineStarts . length ? sourceFile . getEnd ( ) : lineStarts [ i + 1 ] - 1 ;
38+ const lineText = sourceFile . text . substring ( currentLineStart , lineEnd ) ;
39+ const result = lineText . match ( / ^ \s * \/ \/ \s * # ( e n d ) ? r e g i o n (?: \s + ( .* ) ) ? $ / ) ;
40+ if ( ! result || isInComment ( sourceFile , currentLineStart ) ) {
41+ continue ;
42+ }
5343
54- // For single line comments, combine consecutive ones (2 or more) into
55- // a single span from the start of the first till the end of the last
56- if ( currentComment . kind === SyntaxKind . SingleLineCommentTrivia ) {
57- if ( isFirstSingleLineComment ) {
58- firstSingleLineCommentStart = currentComment . pos ;
59- }
60- isFirstSingleLineComment = false ;
61- lastSingleLineCommentEnd = currentComment . end ;
62- singleLineCommentCount ++ ;
63- }
64- else if ( currentComment . kind === SyntaxKind . MultiLineCommentTrivia ) {
65- combineAndAddMultipleSingleLineComments ( singleLineCommentCount , firstSingleLineCommentStart , lastSingleLineCommentEnd ) ;
66- addOutliningSpanComments ( currentComment , /*autoCollapse*/ false ) ;
67-
68- singleLineCommentCount = 0 ;
69- lastSingleLineCommentEnd = - 1 ;
70- isFirstSingleLineComment = true ;
71- }
44+ if ( ! result [ 1 ] ) {
45+ const span = createTextSpanFromBounds ( sourceFile . text . indexOf ( "//" , currentLineStart ) , lineEnd ) ;
46+ regions . push ( createOutliningSpan ( span , span , /*autoCollapse*/ false , result [ 2 ] || "#region" ) ) ;
47+ }
48+ else {
49+ const region = regions . pop ( ) ;
50+ if ( region ) {
51+ region . textSpan . length = lineEnd - region . textSpan . start ;
52+ region . hintSpan . length = lineEnd - region . textSpan . start ;
53+ out . push ( region ) ;
7254 }
73-
74- combineAndAddMultipleSingleLineComments ( singleLineCommentCount , firstSingleLineCommentStart , lastSingleLineCommentEnd ) ;
7555 }
7656 }
57+ }
7758
78- function combineAndAddMultipleSingleLineComments ( count : number , start : number , end : number ) {
59+ function addOutliningForLeadingCommentsForNode ( n : Node , sourceFile : SourceFile , cancellationToken : CancellationToken , out : Push < OutliningSpan > ) : void {
60+ const comments = ts . getLeadingCommentRangesOfNode ( n , sourceFile ) ;
61+ if ( ! comments ) return ;
7962
80- // Only outline spans of two or more consecutive single line comments
81- if ( count > 1 ) {
82- const multipleSingleLineComments : CommentRange = {
83- kind : SyntaxKind . SingleLineCommentTrivia ,
84- pos : start ,
85- end,
86- } ;
87-
88- addOutliningSpanComments ( multipleSingleLineComments , /*autoCollapse*/ false ) ;
89- }
90- }
63+ let firstSingleLineCommentStart = - 1 ;
64+ let lastSingleLineCommentEnd = - 1 ;
65+ let isFirstSingleLineComment = true ;
66+ let singleLineCommentCount = 0 ;
9167
92- function autoCollapse ( node : Node ) {
93- return isFunctionBlock ( node ) && node . parent . kind !== SyntaxKind . ArrowFunction ;
94- }
68+ for ( const currentComment of comments ) {
69+ cancellationToken . throwIfCancellationRequested ( ) ;
9570
96- function gatherRegions ( ) : void {
97- const lineStarts = sourceFile . getLineStarts ( ) ;
98-
99- for ( let i = 0 ; i < lineStarts . length ; i ++ ) {
100- const currentLineStart = lineStarts [ i ] ;
101- const lineEnd = lineStarts [ i + 1 ] - 1 || sourceFile . getEnd ( ) ;
102- const comment = sourceFile . text . substring ( currentLineStart , lineEnd ) ;
103- const result = comment . match ( regionMatch ) ;
104-
105- if ( result && ! isInComment ( sourceFile , currentLineStart ) ) {
106- if ( ! result [ 1 ] ) {
107- const start = sourceFile . getFullText ( ) . indexOf ( "//" , currentLineStart ) ;
108- const textSpan = createTextSpanFromBounds ( start , lineEnd ) ;
109- const region : OutliningSpan = {
110- textSpan,
111- hintSpan : textSpan ,
112- bannerText : result [ 2 ] || defaultLabel ,
113- autoCollapse : false
114- } ;
115- regions . push ( region ) ;
116- }
117- else {
118- const region = regions . pop ( ) ;
119- if ( region ) {
120- region . textSpan . length = lineEnd - region . textSpan . start ;
121- region . hintSpan . length = lineEnd - region . textSpan . start ;
122- elements . push ( region ) ;
123- }
124- }
71+ // For single line comments, combine consecutive ones (2 or more) into
72+ // a single span from the start of the first till the end of the last
73+ if ( currentComment . kind === SyntaxKind . SingleLineCommentTrivia ) {
74+ if ( isFirstSingleLineComment ) {
75+ firstSingleLineCommentStart = currentComment . pos ;
12576 }
77+ isFirstSingleLineComment = false ;
78+ lastSingleLineCommentEnd = currentComment . end ;
79+ singleLineCommentCount ++ ;
12680 }
127- }
81+ else if ( currentComment . kind === SyntaxKind . MultiLineCommentTrivia ) {
82+ combineAndAddMultipleSingleLineComments ( ) ;
83+ out . push ( makeOutliningSpanFromBounds ( currentComment . pos , currentComment . end ) ) ;
12884
129- function walk ( n : Node ) : void {
130- cancellationToken . throwIfCancellationRequested ( ) ;
131- if ( depth > maxDepth ) {
132- return ;
85+ singleLineCommentCount = 0 ;
86+ lastSingleLineCommentEnd = - 1 ;
87+ isFirstSingleLineComment = true ;
13388 }
89+ }
13490
135- if ( isDeclaration ( n ) ) {
136- addOutliningForLeadingCommentsForNode ( n ) ;
91+ combineAndAddMultipleSingleLineComments ( ) ;
92+
93+ function combineAndAddMultipleSingleLineComments ( ) : void {
94+ // Only outline spans of two or more consecutive single line comments
95+ if ( singleLineCommentCount > 1 ) {
96+ out . push ( makeOutliningSpanFromBounds ( firstSingleLineCommentStart , lastSingleLineCommentEnd ) ) ;
13797 }
98+ }
99+ }
138100
139- switch ( n . kind ) {
140- case SyntaxKind . Block :
141- if ( ! isFunctionBlock ( n ) ) {
142- const parent = n . parent ;
143- const openBrace = findChildOfKind ( n , SyntaxKind . OpenBraceToken , sourceFile ) ;
144- const closeBrace = findChildOfKind ( n , SyntaxKind . CloseBraceToken , sourceFile ) ;
145-
146- // Check if the block is standalone, or 'attached' to some parent statement.
147- // If the latter, we want to collapse the block, but consider its hint span
148- // to be the entire span of the parent.
149- if ( parent . kind === SyntaxKind . DoStatement ||
150- parent . kind === SyntaxKind . ForInStatement ||
151- parent . kind === SyntaxKind . ForOfStatement ||
152- parent . kind === SyntaxKind . ForStatement ||
153- parent . kind === SyntaxKind . IfStatement ||
154- parent . kind === SyntaxKind . WhileStatement ||
155- parent . kind === SyntaxKind . WithStatement ||
156- parent . kind === SyntaxKind . CatchClause ) {
157-
158- addOutliningSpan ( parent , openBrace , closeBrace , autoCollapse ( n ) , /*useFullStart*/ true ) ;
159- break ;
160- }
101+ function makeOutliningSpanFromBounds ( pos : number , end : number ) : OutliningSpan {
102+ return createOutliningSpan ( createTextSpanFromBounds ( pos , end ) ) ;
103+ }
161104
162- if ( parent . kind === SyntaxKind . TryStatement ) {
163- // Could be the try-block, or the finally-block.
164- const tryStatement = < TryStatement > parent ;
165- if ( tryStatement . tryBlock === n ) {
166- addOutliningSpan ( parent , openBrace , closeBrace , autoCollapse ( n ) , /*useFullStart*/ true ) ;
167- break ;
168- }
169- else if ( tryStatement . finallyBlock === n ) {
170- const finallyKeyword = findChildOfKind ( tryStatement , SyntaxKind . FinallyKeyword , sourceFile ) ;
171- if ( finallyKeyword ) {
172- addOutliningSpan ( finallyKeyword , openBrace , closeBrace , autoCollapse ( n ) , /*useFullStart*/ true ) ;
173- break ;
174- }
175- }
176-
177- // fall through.
105+ function getOutliningSpanForNode ( n : Node , sourceFile : SourceFile ) : OutliningSpan | undefined {
106+ switch ( n . kind ) {
107+ case SyntaxKind . Block :
108+ if ( isFunctionBlock ( n ) ) {
109+ return spanForNode ( n . parent , /*autoCollapse*/ n . parent . kind !== SyntaxKind . ArrowFunction ) ;
110+ }
111+ // Check if the block is standalone, or 'attached' to some parent statement.
112+ // If the latter, we want to collapse the block, but consider its hint span
113+ // to be the entire span of the parent.
114+ switch ( n . parent . kind ) {
115+ case SyntaxKind . DoStatement :
116+ case SyntaxKind . ForInStatement :
117+ case SyntaxKind . ForOfStatement :
118+ case SyntaxKind . ForStatement :
119+ case SyntaxKind . IfStatement :
120+ case SyntaxKind . WhileStatement :
121+ case SyntaxKind . WithStatement :
122+ case SyntaxKind . CatchClause :
123+ return spanForNode ( n . parent ) ;
124+ case SyntaxKind . TryStatement :
125+ // Could be the try-block, or the finally-block.
126+ const tryStatement = < TryStatement > n . parent ;
127+ if ( tryStatement . tryBlock === n ) {
128+ return spanForNode ( n . parent ) ;
178129 }
179-
130+ else if ( tryStatement . finallyBlock === n ) {
131+ return spanForNode ( findChildOfKind ( tryStatement , SyntaxKind . FinallyKeyword , sourceFile ) ! ) ;
132+ }
133+ // falls through
134+ default :
180135 // Block was a standalone block. In this case we want to only collapse
181136 // the span of the block, independent of any parent span.
182- const span = createTextSpanFromNode ( n ) ;
183- elements . push ( {
184- textSpan : span ,
185- hintSpan : span ,
186- bannerText : collapseText ,
187- autoCollapse : autoCollapse ( n )
188- } ) ;
189- break ;
190- }
191- // falls through
192-
193- case SyntaxKind . ModuleBlock : {
194- const openBrace = findChildOfKind ( n , SyntaxKind . OpenBraceToken , sourceFile ) ;
195- const closeBrace = findChildOfKind ( n , SyntaxKind . CloseBraceToken , sourceFile ) ;
196- addOutliningSpan ( n . parent , openBrace , closeBrace , autoCollapse ( n ) , /*useFullStart*/ true ) ;
197- break ;
137+ return createOutliningSpan ( createTextSpanFromNode ( n , sourceFile ) ) ;
198138 }
199- case SyntaxKind . ClassDeclaration :
200- case SyntaxKind . InterfaceDeclaration :
201- case SyntaxKind . EnumDeclaration :
202- case SyntaxKind . CaseBlock : {
203- const openBrace = findChildOfKind ( n , SyntaxKind . OpenBraceToken , sourceFile ) ;
204- const closeBrace = findChildOfKind ( n , SyntaxKind . CloseBraceToken , sourceFile ) ;
205- addOutliningSpan ( n , openBrace , closeBrace , autoCollapse ( n ) , /*useFullStart*/ true ) ;
206- break ;
207- }
208- // If the block has no leading keywords and is inside an array literal,
209- // we only want to collapse the span of the block.
210- // Otherwise, the collapsed section will include the end of the previous line.
211- case SyntaxKind . ObjectLiteralExpression :
212- const openBrace = findChildOfKind ( n , SyntaxKind . OpenBraceToken , sourceFile ) ;
213- const closeBrace = findChildOfKind ( n , SyntaxKind . CloseBraceToken , sourceFile ) ;
214- addOutliningSpan ( n , openBrace , closeBrace , autoCollapse ( n ) , /*useFullStart*/ ! isArrayLiteralExpression ( n . parent ) ) ;
215- break ;
216- case SyntaxKind . ArrayLiteralExpression :
217- const openBracket = findChildOfKind ( n , SyntaxKind . OpenBracketToken , sourceFile ) ;
218- const closeBracket = findChildOfKind ( n , SyntaxKind . CloseBracketToken , sourceFile ) ;
219- addOutliningSpan ( n , openBracket , closeBracket , autoCollapse ( n ) , /*useFullStart*/ ! isArrayLiteralExpression ( n . parent ) ) ;
220- break ;
139+ case SyntaxKind . ModuleBlock :
140+ return spanForNode ( n . parent ) ;
141+ case SyntaxKind . ClassDeclaration :
142+ case SyntaxKind . InterfaceDeclaration :
143+ case SyntaxKind . EnumDeclaration :
144+ case SyntaxKind . CaseBlock :
145+ return spanForNode ( n ) ;
146+ case SyntaxKind . ObjectLiteralExpression :
147+ return spanForObjectOrArrayLiteral ( n ) ;
148+ case SyntaxKind . ArrayLiteralExpression :
149+ return spanForObjectOrArrayLiteral ( n , SyntaxKind . OpenBracketToken ) ;
150+ }
151+
152+ function spanForObjectOrArrayLiteral ( node : Node , open : SyntaxKind . OpenBraceToken | SyntaxKind . OpenBracketToken = SyntaxKind . OpenBraceToken ) : OutliningSpan | undefined {
153+ // If the block has no leading keywords and is inside an array literal,
154+ // we only want to collapse the span of the block.
155+ // Otherwise, the collapsed section will include the end of the previous line.
156+ return spanForNode ( node , /*autoCollapse*/ false , /*useFullStart*/ ! isArrayLiteralExpression ( node . parent ) , open ) ;
157+ }
158+
159+ function spanForNode ( hintSpanNode : Node , autoCollapse = false , useFullStart = true , open : SyntaxKind . OpenBraceToken | SyntaxKind . OpenBracketToken = SyntaxKind . OpenBraceToken ) : OutliningSpan | undefined {
160+ const openToken = findChildOfKind ( n , open , sourceFile ) ;
161+ const close = open === SyntaxKind . OpenBraceToken ? SyntaxKind . CloseBraceToken : SyntaxKind . CloseBracketToken ;
162+ const closeToken = findChildOfKind ( n , close , sourceFile ) ;
163+ if ( ! openToken || ! closeToken ) {
164+ return undefined ;
221165 }
222- depth ++ ;
223- forEachChild ( n , walk ) ;
224- depth -- ;
166+ const textSpan = createTextSpanFromBounds ( useFullStart ? openToken . getFullStart ( ) : openToken . getStart ( sourceFile ) , closeToken . getEnd ( ) ) ;
167+ return createOutliningSpan ( textSpan , createTextSpanFromNode ( hintSpanNode , sourceFile ) , autoCollapse ) ;
225168 }
226169 }
170+
171+ function createOutliningSpan ( textSpan : TextSpan , hintSpan : TextSpan = textSpan , autoCollapse = false , bannerText = "..." ) : OutliningSpan {
172+ return { textSpan, hintSpan, bannerText, autoCollapse } ;
173+ }
227174}
0 commit comments