Clean up outliningElementsCollector (#20143)

* Clean up outliningElementsCollector

* Use depthRemaining instead of maxDepth
This commit is contained in:
Andy 2017-11-21 14:27:26 -05:00 committed by GitHub
parent 761c739824
commit 7c5a0ec9c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 227 deletions

View file

@ -1,227 +1,166 @@
/* @internal */
namespace ts.OutliningElementsCollector {
const collapseText = "...";
const maxDepth = 40;
const defaultLabel = "#region";
const regionMatch = new RegExp("^\\s*//\\s*#(end)?region(?:\\s+(.*))?$");
export function collectElements(sourceFile: SourceFile, cancellationToken: CancellationToken): OutliningSpan[] {
const elements: OutliningSpan[] = [];
let depth = 0;
const regions: OutliningSpan[] = [];
const res: OutliningSpan[] = [];
addNodeOutliningSpans(sourceFile, cancellationToken, res);
addRegionOutliningSpans(sourceFile, res);
return res.sort((span1, span2) => span1.textSpan.start - span2.textSpan.start);
}
walk(sourceFile);
gatherRegions();
return elements.sort((span1, span2) => span1.textSpan.start - span2.textSpan.start);
/** If useFullStart is true, then the collapsing span includes leading whitespace, including linebreaks. */
function addOutliningSpan(hintSpanNode: Node, startElement: Node, endElement: Node, autoCollapse: boolean, useFullStart: boolean) {
if (hintSpanNode && startElement && endElement) {
const span: OutliningSpan = {
textSpan: createTextSpanFromBounds(useFullStart ? startElement.getFullStart() : startElement.getStart(), endElement.getEnd()),
hintSpan: createTextSpanFromNode(hintSpanNode, sourceFile),
bannerText: collapseText,
autoCollapse,
};
elements.push(span);
}
}
function addOutliningSpanComments(commentSpan: CommentRange, autoCollapse: boolean) {
if (commentSpan) {
const span: OutliningSpan = {
textSpan: createTextSpanFromBounds(commentSpan.pos, commentSpan.end),
hintSpan: createTextSpanFromBounds(commentSpan.pos, commentSpan.end),
bannerText: collapseText,
autoCollapse,
};
elements.push(span);
}
}
function addOutliningForLeadingCommentsForNode(n: Node) {
const comments = ts.getLeadingCommentRangesOfNode(n, sourceFile);
if (comments) {
let firstSingleLineCommentStart = -1;
let lastSingleLineCommentEnd = -1;
let isFirstSingleLineComment = true;
let singleLineCommentCount = 0;
for (const currentComment of comments) {
cancellationToken.throwIfCancellationRequested();
// For single line comments, combine consecutive ones (2 or more) into
// a single span from the start of the first till the end of the last
if (currentComment.kind === SyntaxKind.SingleLineCommentTrivia) {
if (isFirstSingleLineComment) {
firstSingleLineCommentStart = currentComment.pos;
}
isFirstSingleLineComment = false;
lastSingleLineCommentEnd = currentComment.end;
singleLineCommentCount++;
}
else if (currentComment.kind === SyntaxKind.MultiLineCommentTrivia) {
combineAndAddMultipleSingleLineComments(singleLineCommentCount, firstSingleLineCommentStart, lastSingleLineCommentEnd);
addOutliningSpanComments(currentComment, /*autoCollapse*/ false);
singleLineCommentCount = 0;
lastSingleLineCommentEnd = -1;
isFirstSingleLineComment = true;
}
}
combineAndAddMultipleSingleLineComments(singleLineCommentCount, firstSingleLineCommentStart, lastSingleLineCommentEnd);
}
}
function combineAndAddMultipleSingleLineComments(count: number, start: number, end: number) {
// Only outline spans of two or more consecutive single line comments
if (count > 1) {
const multipleSingleLineComments: CommentRange = {
kind: SyntaxKind.SingleLineCommentTrivia,
pos: start,
end,
};
addOutliningSpanComments(multipleSingleLineComments, /*autoCollapse*/ false);
}
}
function autoCollapse(node: Node) {
return isFunctionBlock(node) && node.parent.kind !== SyntaxKind.ArrowFunction;
}
function gatherRegions(): void {
const lineStarts = sourceFile.getLineStarts();
for (let i = 0; i < lineStarts.length; i++) {
const currentLineStart = lineStarts[i];
const lineEnd = lineStarts[i + 1] - 1 || sourceFile.getEnd();
const comment = sourceFile.text.substring(currentLineStart, lineEnd);
const result = comment.match(regionMatch);
if (result && !isInComment(sourceFile, currentLineStart)) {
if (!result[1]) {
const start = sourceFile.getFullText().indexOf("//", currentLineStart);
const textSpan = createTextSpanFromBounds(start, lineEnd);
const region: OutliningSpan = {
textSpan,
hintSpan: textSpan,
bannerText: result[2] || defaultLabel,
autoCollapse: false
};
regions.push(region);
}
else {
const region = regions.pop();
if (region) {
region.textSpan.length = lineEnd - region.textSpan.start;
region.hintSpan.length = lineEnd - region.textSpan.start;
elements.push(region);
}
}
}
}
}
function walk(n: Node): void {
function addNodeOutliningSpans(sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push<OutliningSpan>): void {
let depthRemaining = 40;
sourceFile.forEachChild(function walk(n) {
if (depthRemaining === 0) return;
cancellationToken.throwIfCancellationRequested();
if (depth > maxDepth) {
return;
}
if (isDeclaration(n)) {
addOutliningForLeadingCommentsForNode(n);
addOutliningForLeadingCommentsForNode(n, sourceFile, cancellationToken, out);
}
switch (n.kind) {
case SyntaxKind.Block:
if (!isFunctionBlock(n)) {
const parent = n.parent;
const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile);
const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile);
const span = getOutliningSpanForNode(n, sourceFile);
if (span) out.push(span);
// Check if the block is standalone, or 'attached' to some parent statement.
// If the latter, we want to collapse the block, but consider its hint span
// to be the entire span of the parent.
if (parent.kind === SyntaxKind.DoStatement ||
parent.kind === SyntaxKind.ForInStatement ||
parent.kind === SyntaxKind.ForOfStatement ||
parent.kind === SyntaxKind.ForStatement ||
parent.kind === SyntaxKind.IfStatement ||
parent.kind === SyntaxKind.WhileStatement ||
parent.kind === SyntaxKind.WithStatement ||
parent.kind === SyntaxKind.CatchClause) {
depthRemaining--;
n.forEachChild(walk);
depthRemaining++;
});
}
addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
if (parent.kind === SyntaxKind.TryStatement) {
// Could be the try-block, or the finally-block.
const tryStatement = <TryStatement>parent;
if (tryStatement.tryBlock === n) {
addOutliningSpan(parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
else if (tryStatement.finallyBlock === n) {
const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile);
if (finallyKeyword) {
addOutliningSpan(finallyKeyword, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
}
// fall through.
}
// Block was a standalone block. In this case we want to only collapse
// the span of the block, independent of any parent span.
const span = createTextSpanFromNode(n);
elements.push({
textSpan: span,
hintSpan: span,
bannerText: collapseText,
autoCollapse: autoCollapse(n)
});
break;
}
// falls through
case SyntaxKind.ModuleBlock: {
const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile);
const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile);
addOutliningSpan(n.parent, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.CaseBlock: {
const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile);
const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile);
addOutliningSpan(n, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ true);
break;
}
// If the block has no leading keywords and is inside an array literal,
// we only want to collapse the span of the block.
// Otherwise, the collapsed section will include the end of the previous line.
case SyntaxKind.ObjectLiteralExpression:
const openBrace = findChildOfKind(n, SyntaxKind.OpenBraceToken, sourceFile);
const closeBrace = findChildOfKind(n, SyntaxKind.CloseBraceToken, sourceFile);
addOutliningSpan(n, openBrace, closeBrace, autoCollapse(n), /*useFullStart*/ !isArrayLiteralExpression(n.parent));
break;
case SyntaxKind.ArrayLiteralExpression:
const openBracket = findChildOfKind(n, SyntaxKind.OpenBracketToken, sourceFile);
const closeBracket = findChildOfKind(n, SyntaxKind.CloseBracketToken, sourceFile);
addOutliningSpan(n, openBracket, closeBracket, autoCollapse(n), /*useFullStart*/ !isArrayLiteralExpression(n.parent));
break;
function addRegionOutliningSpans(sourceFile: SourceFile, out: Push<OutliningSpan>): void {
const regions: OutliningSpan[] = [];
const lineStarts = sourceFile.getLineStarts();
for (let i = 0; i < lineStarts.length; i++) {
const currentLineStart = lineStarts[i];
const lineEnd = i + 1 === lineStarts.length ? sourceFile.getEnd() : lineStarts[i + 1] - 1;
const lineText = sourceFile.text.substring(currentLineStart, lineEnd);
const result = lineText.match(/^\s*\/\/\s*#(end)?region(?:\s+(.*))?$/);
if (!result || isInComment(sourceFile, currentLineStart)) {
continue;
}
if (!result[1]) {
const span = createTextSpanFromBounds(sourceFile.text.indexOf("//", currentLineStart), lineEnd);
regions.push(createOutliningSpan(span, span, /*autoCollapse*/ false, result[2] || "#region"));
}
else {
const region = regions.pop();
if (region) {
region.textSpan.length = lineEnd - region.textSpan.start;
region.hintSpan.length = lineEnd - region.textSpan.start;
out.push(region);
}
}
depth++;
forEachChild(n, walk);
depth--;
}
}
function addOutliningForLeadingCommentsForNode(n: Node, sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push<OutliningSpan>): void {
const comments = getLeadingCommentRangesOfNode(n, sourceFile);
if (!comments) return;
let firstSingleLineCommentStart = -1;
let lastSingleLineCommentEnd = -1;
let singleLineCommentCount = 0;
for (const { kind, pos, end } of comments) {
cancellationToken.throwIfCancellationRequested();
switch (kind) {
case SyntaxKind.SingleLineCommentTrivia:
// For single line comments, combine consecutive ones (2 or more) into
// a single span from the start of the first till the end of the last
if (singleLineCommentCount === 0) {
firstSingleLineCommentStart = pos;
}
lastSingleLineCommentEnd = end;
singleLineCommentCount++;
break;
case SyntaxKind.MultiLineCommentTrivia:
combineAndAddMultipleSingleLineComments();
out.push(createOutliningSpanFromBounds(pos, end));
singleLineCommentCount = 0;
break;
default:
Debug.assertNever(kind);
}
}
combineAndAddMultipleSingleLineComments();
function combineAndAddMultipleSingleLineComments(): void {
// Only outline spans of two or more consecutive single line comments
if (singleLineCommentCount > 1) {
out.push(createOutliningSpanFromBounds(firstSingleLineCommentStart, lastSingleLineCommentEnd));
}
}
}
function createOutliningSpanFromBounds(pos: number, end: number): OutliningSpan {
return createOutliningSpan(createTextSpanFromBounds(pos, end));
}
function getOutliningSpanForNode(n: Node, sourceFile: SourceFile): OutliningSpan | undefined {
switch (n.kind) {
case SyntaxKind.Block:
if (isFunctionBlock(n)) {
return spanForNode(n.parent, /*autoCollapse*/ n.parent.kind !== SyntaxKind.ArrowFunction);
}
// Check if the block is standalone, or 'attached' to some parent statement.
// If the latter, we want to collapse the block, but consider its hint span
// to be the entire span of the parent.
switch (n.parent.kind) {
case SyntaxKind.DoStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
case SyntaxKind.ForStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.WithStatement:
case SyntaxKind.CatchClause:
return spanForNode(n.parent);
case SyntaxKind.TryStatement:
// Could be the try-block, or the finally-block.
const tryStatement = <TryStatement>n.parent;
if (tryStatement.tryBlock === n) {
return spanForNode(n.parent);
}
else if (tryStatement.finallyBlock === n) {
return spanForNode(findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!);
}
// falls through
default:
// Block was a standalone block. In this case we want to only collapse
// the span of the block, independent of any parent span.
return createOutliningSpan(createTextSpanFromNode(n, sourceFile));
}
case SyntaxKind.ModuleBlock:
return spanForNode(n.parent);
case SyntaxKind.ClassDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.CaseBlock:
return spanForNode(n);
case SyntaxKind.ObjectLiteralExpression:
return spanForObjectOrArrayLiteral(n);
case SyntaxKind.ArrayLiteralExpression:
return spanForObjectOrArrayLiteral(n, SyntaxKind.OpenBracketToken);
}
function spanForObjectOrArrayLiteral(node: Node, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined {
// If the block has no leading keywords and is inside an array literal,
// we only want to collapse the span of the block.
// Otherwise, the collapsed section will include the end of the previous line.
return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent), open);
}
function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined {
const openToken = findChildOfKind(n, open, sourceFile);
const close = open === SyntaxKind.OpenBraceToken ? SyntaxKind.CloseBraceToken : SyntaxKind.CloseBracketToken;
const closeToken = findChildOfKind(n, close, sourceFile);
if (!openToken || !closeToken) {
return undefined;
}
const textSpan = createTextSpanFromBounds(useFullStart ? openToken.getFullStart() : openToken.getStart(sourceFile), closeToken.getEnd());
return createOutliningSpan(textSpan, createTextSpanFromNode(hintSpanNode, sourceFile), autoCollapse);
}
}
function createOutliningSpan(textSpan: TextSpan, hintSpan: TextSpan = textSpan, autoCollapse = false, bannerText = "..."): OutliningSpan {
return { textSpan, hintSpan, bannerText, autoCollapse };
}
}

View file

@ -1,6 +1,6 @@
/// <reference path="fourslash.ts"/>
////// interface
////// interface
////interface IFoo[| {
//// getDist(): number;
////}|]
@ -63,9 +63,9 @@
////}|])
////
////// trivia handeling
////class ClassFooWithTrivia[| /* some comments */
////class ClassFooWithTrivia[| /* some comments */
//// /* more trivia */ {
////
////
////
//// /*some trailing trivia */
////}|] /* even more */
@ -85,8 +85,8 @@
//// [
//// [
//// [
//// 1,2,3
//// ]
//// 1,2,3
//// ]
//// ]
//// ]
//// ]

View file

@ -1,6 +1,6 @@
/// <reference path="fourslash.ts"/>
////// interface
////// interface
////interface IFoo[| {
//// getDist(): number;
////}|]
@ -63,9 +63,9 @@
////}|])
////
////// trivia handeling
////class ClassFooWithTrivia[| /* some comments */
////class ClassFooWithTrivia[| /* some comments */
//// /* more trivia */ {
////
////
////
//// /*some trailing trivia */
////}|] /* even more */
@ -85,8 +85,8 @@
//// [
//// [
//// [
//// 1,2,3
//// ]
//// 1,2,3
//// ]
//// ]
//// ]
//// ]

View file

@ -1,6 +1,6 @@
/// <reference path="fourslash.ts"/>
////// interface
////// interface
////interface IFoo[| {
//// getDist(): number;
////}|]
@ -63,9 +63,9 @@
////}|])
////
////// trivia handeling
////class ClassFooWithTrivia[| /* some comments */
////class ClassFooWithTrivia[| /* some comments */
//// /* more trivia */ {
////
////
////
//// /*some trailing trivia */
////}|] /* even more */
@ -85,8 +85,8 @@
//// [
//// [
//// [
//// 1,2,3
//// ]
//// 1,2,3
//// ]
//// ]
//// ]
//// ]