Skip to content

Commit 3eafd3d

Browse files
author
andy-ms
committed
Clean up outliningElementsCollector
1 parent 6c4c10c commit 3eafd3d

File tree

1 file changed

+140
-193
lines changed

1 file changed

+140
-193
lines changed
Lines changed: 140 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,174 @@
11
/* @internal */
22
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-
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*#(end)?region(?:\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

Comments
 (0)