diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2e6c3ae4f866e..7deb2c823cdc1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4330,6 +4330,10 @@ namespace ts { return positionsAreOnSameLine(range1.end, getStartPositionOfRange(range2, sourceFile), sourceFile); } + export function isNodeArrayMultiLine(list: NodeArray, sourceFile: SourceFile): boolean { + return !positionsAreOnSameLine(list.pos, list.end, sourceFile); + } + export function positionsAreOnSameLine(pos1: number, pos2: number, sourceFile: SourceFile) { return pos1 === pos2 || getLineOfLocalPosition(sourceFile, pos1) === getLineOfLocalPosition(sourceFile, pos2); diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 6f834512f0b48..650a107f4a5e7 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -153,8 +153,8 @@ namespace ts.OutliningElementsCollector { 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); + if (isFunctionLike(n.parent)) { + return functionSpan(n.parent, n as Block, sourceFile); } // 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 @@ -225,18 +225,26 @@ namespace ts.OutliningElementsCollector { return spanForNode(node, /*autoCollapse*/ false, /*useFullStart*/ !isArrayLiteralExpression(node.parent) && !isCallExpression(node.parent), open); } - function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken): OutliningSpan | undefined { + function spanForNode(hintSpanNode: Node, autoCollapse = false, useFullStart = true, open: SyntaxKind.OpenBraceToken | SyntaxKind.OpenBracketToken = SyntaxKind.OpenBraceToken, close: SyntaxKind = open === SyntaxKind.OpenBraceToken ? SyntaxKind.CloseBraceToken : SyntaxKind.CloseBracketToken): 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, OutliningSpanKind.Code, createTextSpanFromNode(hintSpanNode, sourceFile), autoCollapse); + return openToken && closeToken && spanBetweenTokens(openToken, closeToken, hintSpanNode, sourceFile, autoCollapse, useFullStart); } } + function functionSpan(node: FunctionLike, body: Block, sourceFile: SourceFile): OutliningSpan | undefined { + const openToken = isNodeArrayMultiLine(node.parameters, sourceFile) + ? findChildOfKind(node, SyntaxKind.OpenParenToken, sourceFile) + : findChildOfKind(body, SyntaxKind.OpenBraceToken, sourceFile); + const closeToken = findChildOfKind(body, SyntaxKind.CloseBraceToken, sourceFile); + return openToken && closeToken && spanBetweenTokens(openToken, closeToken, node.parent, sourceFile, /*autoCollapse*/ node.parent.kind !== SyntaxKind.ArrowFunction); + } + + function spanBetweenTokens(openToken: Node, closeToken: Node, hintSpanNode: Node, sourceFile: SourceFile, autoCollapse = false, useFullStart = true): OutliningSpan { + const textSpan = createTextSpanFromBounds(useFullStart ? openToken.getFullStart() : openToken.getStart(sourceFile), closeToken.getEnd()); + return createOutliningSpan(textSpan, OutliningSpanKind.Code, createTextSpanFromNode(hintSpanNode, sourceFile), autoCollapse); + } + function createOutliningSpan(textSpan: TextSpan, kind: OutliningSpanKind, hintSpan: TextSpan = textSpan, autoCollapse = false, bannerText = "..."): OutliningSpan { return { textSpan, kind, hintSpan, bannerText, autoCollapse }; } diff --git a/tests/cases/fourslash/outliningSpansForFunction.ts b/tests/cases/fourslash/outliningSpansForFunction.ts new file mode 100644 index 0000000000000..acc00eedbee3a --- /dev/null +++ b/tests/cases/fourslash/outliningSpansForFunction.ts @@ -0,0 +1,14 @@ +/// + +////function f(x: number, y: number)[| { +//// return x + y; +////}|] +//// +////function g[|( +//// x: number, +//// y: number, +////): number { +//// return x + y; +////}|] + +verify.outliningSpansInCurrentFile(test.ranges());