1
1
/* @internal */
2
2
namespace ts . OutliningElementsCollector {
3
- const collapseText = "..." ;
4
- const maxDepth = 40 ;
5
- const defaultLabel = "#region" ;
6
- const regionMatch = new RegExp ( "^\\s*//\\s*#(end)?region(?:\\s+(.*))?$" ) ;
7
-
8
3
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
+ }
12
9
13
- walk ( sourceFile ) ;
14
- gatherRegions ( ) ;
15
- return elements . sort ( ( span1 , span2 ) => span1 . textSpan . start - span2 . textSpan . start ) ;
10
+ function addNodeOutliningSpans ( sourceFile : SourceFile , cancellationToken : CancellationToken , out : Push < OutliningSpan > ) : void {
11
+ let depthRemaining = 40 ;
12
+ sourceFile . forEachChild ( function walk ( n ) {
13
+ if ( depthRemaining === 0 ) return ;
14
+ cancellationToken . throwIfCancellationRequested ( ) ;
16
15
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 ) ;
16
+ if ( isDeclaration ( n ) ) {
17
+ addOutliningForLeadingCommentsForNode ( n , sourceFile , cancellationToken , out ) ;
27
18
}
28
- }
29
19
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 ) ;
39
- }
40
- }
20
+ const span = getOutliningSpanForNode ( n , sourceFile ) ;
21
+ if ( span ) out . push ( span ) ;
41
22
42
- function addOutliningForLeadingCommentsForNode ( n : Node ) {
43
- const comments = ts . getLeadingCommentRangesOfNode ( n , sourceFile ) ;
23
+ depthRemaining -- ;
24
+ n . forEachChild ( walk ) ;
25
+ depthRemaining ++ ;
26
+ } ) ;
27
+ }
44
28
45
- if ( comments ) {
46
- let firstSingleLineCommentStart = - 1 ;
47
- let lastSingleLineCommentEnd = - 1 ;
48
- let isFirstSingleLineComment = true ;
49
- let singleLineCommentCount = 0 ;
29
+ function addRegionOutliningSpans ( sourceFile : SourceFile , out : Push < OutliningSpan > ) : void {
30
+ const regions : OutliningSpan [ ] = [ ] ;
31
+ const lineStarts = sourceFile . getLineStarts ( ) ;
32
+ for ( let i = 0 ; i < lineStarts . length ; i ++ ) {
33
+ const currentLineStart = lineStarts [ i ] ;
34
+ const lineEnd = i + 1 === lineStarts . length ? sourceFile . getEnd ( ) : lineStarts [ i + 1 ] - 1 ;
35
+ const lineText = sourceFile . text . substring ( currentLineStart , lineEnd ) ;
36
+ const result = lineText . match ( / ^ \s * \/ \/ \s * # ( e n d ) ? r e g i o n (?: \s + ( .* ) ) ? $ / ) ;
37
+ if ( ! result || isInComment ( sourceFile , currentLineStart ) ) {
38
+ continue ;
39
+ }
50
40
51
- for ( const currentComment of comments ) {
52
- cancellationToken . throwIfCancellationRequested ( ) ;
41
+ if ( ! result [ 1 ] ) {
42
+ const span = createTextSpanFromBounds ( sourceFile . text . indexOf ( "//" , currentLineStart ) , lineEnd ) ;
43
+ regions . push ( createOutliningSpan ( span , span , /*autoCollapse*/ false , result [ 2 ] || "#region" ) ) ;
44
+ }
45
+ else {
46
+ const region = regions . pop ( ) ;
47
+ if ( region ) {
48
+ region . textSpan . length = lineEnd - region . textSpan . start ;
49
+ region . hintSpan . length = lineEnd - region . textSpan . start ;
50
+ out . push ( region ) ;
51
+ }
52
+ }
53
+ }
54
+ }
53
55
56
+ function addOutliningForLeadingCommentsForNode ( n : Node , sourceFile : SourceFile , cancellationToken : CancellationToken , out : Push < OutliningSpan > ) : void {
57
+ const comments = getLeadingCommentRangesOfNode ( n , sourceFile ) ;
58
+ if ( ! comments ) return ;
59
+ let firstSingleLineCommentStart = - 1 ;
60
+ let lastSingleLineCommentEnd = - 1 ;
61
+ let singleLineCommentCount = 0 ;
62
+ for ( const { kind, pos, end } of comments ) {
63
+ cancellationToken . throwIfCancellationRequested ( ) ;
64
+ switch ( kind ) {
65
+ case SyntaxKind . SingleLineCommentTrivia :
54
66
// For single line comments, combine consecutive ones (2 or more) into
55
67
// 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 ;
68
+ if ( singleLineCommentCount === 0 ) {
69
+ firstSingleLineCommentStart = pos ;
71
70
}
72
- }
73
-
74
- combineAndAddMultipleSingleLineComments ( singleLineCommentCount , firstSingleLineCommentStart , lastSingleLineCommentEnd ) ;
71
+ lastSingleLineCommentEnd = end ;
72
+ singleLineCommentCount ++ ;
73
+ break ;
74
+ case SyntaxKind . MultiLineCommentTrivia :
75
+ combineAndAddMultipleSingleLineComments ( ) ;
76
+ out . push ( createOutliningSpanFromBounds ( pos , end ) ) ;
77
+ singleLineCommentCount = 0 ;
78
+ break ;
79
+ default :
80
+ Debug . assertNever ( kind ) ;
75
81
}
76
82
}
83
+ combineAndAddMultipleSingleLineComments ( ) ;
77
84
78
- function combineAndAddMultipleSingleLineComments ( count : number , start : number , end : number ) {
79
-
85
+ function combineAndAddMultipleSingleLineComments ( ) : void {
80
86
// 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 ) ;
87
+ if ( singleLineCommentCount > 1 ) {
88
+ out . push ( createOutliningSpanFromBounds ( firstSingleLineCommentStart , lastSingleLineCommentEnd ) ) ;
89
89
}
90
90
}
91
+ }
91
92
92
- function autoCollapse ( node : Node ) {
93
- return isFunctionBlock ( node ) && node . parent . kind !== SyntaxKind . ArrowFunction ;
94
- }
95
-
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 ) ;
93
+ function createOutliningSpanFromBounds ( pos : number , end : number ) : OutliningSpan {
94
+ return createOutliningSpan ( createTextSpanFromBounds ( pos , end ) ) ;
95
+ }
104
96
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
- }
97
+ function getOutliningSpanForNode ( n : Node , sourceFile : SourceFile ) : OutliningSpan | undefined {
98
+ switch ( n . kind ) {
99
+ case SyntaxKind . Block :
100
+ if ( isFunctionBlock ( n ) ) {
101
+ return spanForNode ( n . parent , /*autoCollapse*/ n . parent . kind !== SyntaxKind . ArrowFunction ) ;
125
102
}
126
- }
127
- }
128
-
129
- function walk ( n : Node ) : void {
130
- cancellationToken . throwIfCancellationRequested ( ) ;
131
- if ( depth > maxDepth ) {
132
- return ;
133
- }
134
-
135
- if ( isDeclaration ( n ) ) {
136
- addOutliningForLeadingCommentsForNode ( n ) ;
137
- }
138
-
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 ;
103
+ // Check if the block is standalone, or 'attached' to some parent statement.
104
+ // If the latter, we want to collapse the block, but consider its hint span
105
+ // to be the entire span of the parent.
106
+ switch ( n . parent . kind ) {
107
+ case SyntaxKind . DoStatement :
108
+ case SyntaxKind . ForInStatement :
109
+ case SyntaxKind . ForOfStatement :
110
+ case SyntaxKind . ForStatement :
111
+ case SyntaxKind . IfStatement :
112
+ case SyntaxKind . WhileStatement :
113
+ case SyntaxKind . WithStatement :
114
+ case SyntaxKind . CatchClause :
115
+ return spanForNode ( n . parent ) ;
116
+ case SyntaxKind . TryStatement :
117
+ // Could be the try-block, or the finally-block.
118
+ const tryStatement = < TryStatement > n . parent ;
119
+ if ( tryStatement . tryBlock === n ) {
120
+ return spanForNode ( n . parent ) ;
160
121
}
161
-
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.
122
+ else if ( tryStatement . finallyBlock === n ) {
123
+ return spanForNode ( findChildOfKind ( tryStatement , SyntaxKind . FinallyKeyword , sourceFile ) ! ) ;
178
124
}
179
-
125
+ // falls through
126
+ default :
180
127
// Block was a standalone block. In this case we want to only collapse
181
128
// 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 ;
198
- }
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 ;
129
+ return createOutliningSpan ( createTextSpanFromNode ( n , sourceFile ) ) ;
207
130
}
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 ;
131
+ case SyntaxKind . ModuleBlock :
132
+ return spanForNode ( n . parent ) ;
133
+ case SyntaxKind . ClassDeclaration :
134
+ case SyntaxKind . InterfaceDeclaration :
135
+ case SyntaxKind . EnumDeclaration :
136
+ case SyntaxKind . CaseBlock :
137
+ return spanForNode ( n ) ;
138
+ case SyntaxKind . ObjectLiteralExpression :
139
+ return spanForObjectOrArrayLiteral ( n ) ;
140
+ case SyntaxKind . ArrayLiteralExpression :
141
+ return spanForObjectOrArrayLiteral ( n , SyntaxKind . OpenBracketToken ) ;
142
+ }
143
+
144
+ function spanForObjectOrArrayLiteral ( node : Node , open : SyntaxKind . OpenBraceToken | SyntaxKind . OpenBracketToken = SyntaxKind . OpenBraceToken ) : OutliningSpan | undefined {
145
+ // If the block has no leading keywords and is inside an array literal,
146
+ // we only want to collapse the span of the block.
147
+ // Otherwise, the collapsed section will include the end of the previous line.
148
+ return spanForNode ( node , /*autoCollapse*/ false , /*useFullStart*/ ! isArrayLiteralExpression ( node . parent ) , open ) ;
149
+ }
150
+
151
+ function spanForNode ( hintSpanNode : Node , autoCollapse = false , useFullStart = true , open : SyntaxKind . OpenBraceToken | SyntaxKind . OpenBracketToken = SyntaxKind . OpenBraceToken ) : OutliningSpan | undefined {
152
+ const openToken = findChildOfKind ( n , open , sourceFile ) ;
153
+ const close = open === SyntaxKind . OpenBraceToken ? SyntaxKind . CloseBraceToken : SyntaxKind . CloseBracketToken ;
154
+ const closeToken = findChildOfKind ( n , close , sourceFile ) ;
155
+ if ( ! openToken || ! closeToken ) {
156
+ return undefined ;
221
157
}
222
- depth ++ ;
223
- forEachChild ( n , walk ) ;
224
- depth -- ;
158
+ const textSpan = createTextSpanFromBounds ( useFullStart ? openToken . getFullStart ( ) : openToken . getStart ( sourceFile ) , closeToken . getEnd ( ) ) ;
159
+ return createOutliningSpan ( textSpan , createTextSpanFromNode ( hintSpanNode , sourceFile ) , autoCollapse ) ;
225
160
}
226
161
}
162
+
163
+ function createOutliningSpan ( textSpan : TextSpan , hintSpan : TextSpan = textSpan , autoCollapse = false , bannerText = "..." ) : OutliningSpan {
164
+ return { textSpan, hintSpan, bannerText, autoCollapse } ;
165
+ }
227
166
}
0 commit comments