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 ) ;
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 ;
27
16
}
28
- }
17
+ cancellationToken . throwIfCancellationRequested ( ) ;
29
18
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 ) ;
39
21
}
40
- }
41
22
42
- function addOutliningForLeadingCommentsForNode ( n : Node ) {
43
- const comments = ts . getLeadingCommentRangesOfNode ( n , sourceFile ) ;
23
+ const span = getOutliningSpanForNode ( n , sourceFile ) ;
24
+ if ( span ) out . push ( span ) ;
44
25
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
+ }
50
31
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
+ }
53
43
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 ) ;
72
54
}
73
-
74
- combineAndAddMultipleSingleLineComments ( singleLineCommentCount , firstSingleLineCommentStart , lastSingleLineCommentEnd ) ;
75
55
}
76
56
}
57
+ }
77
58
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 ;
79
62
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 ;
91
67
92
- function autoCollapse ( node : Node ) {
93
- return isFunctionBlock ( node ) && node . parent . kind !== SyntaxKind . ArrowFunction ;
94
- }
68
+ for ( const currentComment of comments ) {
69
+ cancellationToken . throwIfCancellationRequested ( ) ;
95
70
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 ;
125
76
}
77
+ isFirstSingleLineComment = false ;
78
+ lastSingleLineCommentEnd = currentComment . end ;
79
+ singleLineCommentCount ++ ;
126
80
}
127
- }
81
+ else if ( currentComment . kind === SyntaxKind . MultiLineCommentTrivia ) {
82
+ combineAndAddMultipleSingleLineComments ( ) ;
83
+ out . push ( makeOutliningSpanFromBounds ( currentComment . pos , currentComment . end ) ) ;
128
84
129
- function walk ( n : Node ) : void {
130
- cancellationToken . throwIfCancellationRequested ( ) ;
131
- if ( depth > maxDepth ) {
132
- return ;
85
+ singleLineCommentCount = 0 ;
86
+ lastSingleLineCommentEnd = - 1 ;
87
+ isFirstSingleLineComment = true ;
133
88
}
89
+ }
134
90
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 ) ) ;
137
97
}
98
+ }
99
+ }
138
100
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
+ }
161
104
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 ) ;
178
129
}
179
-
130
+ else if ( tryStatement . finallyBlock === n ) {
131
+ return spanForNode ( findChildOfKind ( tryStatement , SyntaxKind . FinallyKeyword , sourceFile ) ! ) ;
132
+ }
133
+ // falls through
134
+ default :
180
135
// Block was a standalone block. In this case we want to only collapse
181
136
// 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 ) ) ;
198
138
}
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 ;
221
165
}
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 ) ;
225
168
}
226
169
}
170
+
171
+ function createOutliningSpan ( textSpan : TextSpan , hintSpan : TextSpan = textSpan , autoCollapse = false , bannerText = "..." ) : OutliningSpan {
172
+ return { textSpan, hintSpan, bannerText, autoCollapse } ;
173
+ }
227
174
}
0 commit comments