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