Clean up outliningElementsCollector (#20143)
* Clean up outliningElementsCollector * Use depthRemaining instead of maxDepth
This commit is contained in:
parent
761c739824
commit
7c5a0ec9c6
|
@ -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 };
|
||||
}
|
||||
}
|
|
@ -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
|
||||
//// ]
|
||||
//// ]
|
||||
//// ]
|
||||
//// ]
|
||||
|
|
|
@ -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
|
||||
//// ]
|
||||
//// ]
|
||||
//// ]
|
||||
//// ]
|
||||
|
|
|
@ -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
|
||||
//// ]
|
||||
//// ]
|
||||
//// ]
|
||||
//// ]
|
||||
|
|
Loading…
Reference in a new issue