diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 7807d10b0f9..ff14e5b770b 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2419,7 +2419,7 @@ Actual: ${stringify(fullActual)}`); Harness.IO.log(stringify(spans)); } - public verifyOutliningSpans(spans: Range[], kind?: "comment" | "region" | "code") { + public verifyOutliningSpans(spans: Range[], kind?: "comment" | "region" | "code" | "imports") { const actual = this.languageService.getOutliningSpans(this.activeFile.fileName); if (actual.length !== spans.length) { @@ -4247,7 +4247,7 @@ namespace FourSlashInterface { this.state.verifyCurrentNameOrDottedNameSpanText(text); } - public outliningSpansInCurrentFile(spans: FourSlash.Range[], kind?: "comment" | "region" | "code") { + public outliningSpansInCurrentFile(spans: FourSlash.Range[], kind?: "comment" | "region" | "code" | "imports") { this.state.verifyOutliningSpans(spans, kind); } diff --git a/src/services/outliningElementsCollector.ts b/src/services/outliningElementsCollector.ts index 10684c268be..91754e2ac76 100644 --- a/src/services/outliningElementsCollector.ts +++ b/src/services/outliningElementsCollector.ts @@ -9,7 +9,27 @@ namespace ts.OutliningElementsCollector { function addNodeOutliningSpans(sourceFile: SourceFile, cancellationToken: CancellationToken, out: Push): void { let depthRemaining = 40; - sourceFile.forEachChild(function walk(n) { + let current = 0; + const statements = sourceFile.statements; + const n = statements.length; + while (current < n) { + while (current < n && !isAnyImportSyntax(statements[current])) { + visitNonImportNode(statements[current]); + current++; + } + if (current === n) break; + const firstImport = current; + while (current < n && isAnyImportSyntax(statements[current])) { + addOutliningForLeadingCommentsForNode(statements[current], sourceFile, cancellationToken, out); + current++; + } + const lastImport = current - 1; + if (lastImport !== firstImport) { + out.push(createOutliningSpanFromBounds(findChildOfKind(statements[firstImport], SyntaxKind.ImportKeyword, sourceFile)!.getStart(sourceFile), statements[lastImport].getEnd(), OutliningSpanKind.Imports)); + } + } + + function visitNonImportNode(n: Node) { if (depthRemaining === 0) return; cancellationToken.throwIfCancellationRequested(); @@ -23,17 +43,17 @@ namespace ts.OutliningElementsCollector { depthRemaining--; if (isIfStatement(n) && n.elseStatement && isIfStatement(n.elseStatement)) { // Consider an 'else if' to be on the same depth as the 'if'. - walk(n.expression); - walk(n.thenStatement); + visitNonImportNode(n.expression); + visitNonImportNode(n.thenStatement); depthRemaining++; - walk(n.elseStatement); + visitNonImportNode(n.elseStatement); depthRemaining--; } else { - n.forEachChild(walk); + n.forEachChild(visitNonImportNode); } depthRemaining++; - }); + } } function addRegionOutliningSpans(sourceFile: SourceFile, out: Push): void { diff --git a/src/services/types.ts b/src/services/types.ts index 31baf7df399..69e33ce6732 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -824,9 +824,17 @@ namespace ts { } export const enum OutliningSpanKind { + /** Single or multi-line comments */ Comment = "comment", + + /** Sections marked by '// #region' and '// #endregion' comments */ Region = "region", - Code = "code" + + /** Declarations and expressions */ + Code = "code", + + /** Contiguous blocks of import declarations */ + Imports = "imports" } export const enum OutputFileType { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index cda7e745657..5d1ef5e7a75 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4890,9 +4890,14 @@ declare namespace ts { kind: OutliningSpanKind; } enum OutliningSpanKind { + /** Single or multi-line comments */ Comment = "comment", + /** Sections marked by '// #region' and '// #endregion' comments */ Region = "region", - Code = "code" + /** Declarations and expressions */ + Code = "code", + /** Contiguous blocks of import declarations */ + Imports = "imports" } enum OutputFileType { JavaScript = 0, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 2f7a9537504..211c2fbbdf4 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4890,9 +4890,14 @@ declare namespace ts { kind: OutliningSpanKind; } enum OutliningSpanKind { + /** Single or multi-line comments */ Comment = "comment", + /** Sections marked by '// #region' and '// #endregion' comments */ Region = "region", - Code = "code" + /** Declarations and expressions */ + Code = "code", + /** Contiguous blocks of import declarations */ + Imports = "imports" } enum OutputFileType { JavaScript = 0, diff --git a/tests/cases/fourslash/getOutliningSpansForImports.ts b/tests/cases/fourslash/getOutliningSpansForImports.ts new file mode 100644 index 00000000000..703c4f5891c --- /dev/null +++ b/tests/cases/fourslash/getOutliningSpansForImports.ts @@ -0,0 +1,20 @@ +/// + + +////[|import * as ns from "mod"; +//// +////import d from "mod"; +////import { a, b, c } from "mod"; +//// +////import r = require("mod");|] +//// +////// statement +////var x = 0; +//// +////// another set of imports +////[|import * as ns from "mod"; +////import d from "mod"; +////import { a, b, c } from "mod"; +////import r = require("mod");|] + +verify.outliningSpansInCurrentFile(test.ranges(), "imports"); diff --git a/tests/cases/fourslash/incrementalParsingWithJsDoc.ts b/tests/cases/fourslash/incrementalParsingWithJsDoc.ts index cd709530f9c..324cc0608ab 100644 --- a/tests/cases/fourslash/incrementalParsingWithJsDoc.ts +++ b/tests/cases/fourslash/incrementalParsingWithJsDoc.ts @@ -1,8 +1,8 @@ /// -////import a from 'a/aaaaaaa/aaaaaaa/aaaaaa/aaaaaaa'; +////[|import a from 'a/aaaaaaa/aaaaaaa/aaaaaa/aaaaaaa'; /////**/import b from 'b'; -////import c from 'c'; +////import c from 'c';|] //// ////[|/** @internal */|] ////export class LanguageIdentifier[| { }|]