Skip to content

Commit 66b8d42

Browse files
committed
Add a request to get all tests within a file
1 parent 6060db2 commit 66b8d42

File tree

8 files changed

+162
-16
lines changed

8 files changed

+162
-16
lines changed

Sources/LanguageServerProtocol/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ add_library(LanguageServerProtocol STATIC
5050
Requests/DocumentSemanticTokensRangeRequest.swift
5151
Requests/DocumentSemanticTokensRequest.swift
5252
Requests/DocumentSymbolRequest.swift
53+
Requests/DocumentTestsRequest.swift
5354
Requests/ExecuteCommandRequest.swift
5455
Requests/FoldingRangeRequest.swift
5556
Requests/FormattingRequests.swift

Sources/LanguageServerProtocol/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public let builtinRequests: [_RequestType.Type] = [
4545
DocumentSemanticTokensRangeRequest.self,
4646
DocumentSemanticTokensRequest.self,
4747
DocumentSymbolRequest.self,
48+
DocumentTestsRequest.self,
4849
ExecuteCommandRequest.self,
4950
FoldingRangeRequest.self,
5051
HoverRequest.self,
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
/// A request that returns symbols for all the test classes and test methods within a file.
14+
///
15+
/// **(LSP Extension)**
16+
public struct DocumentTestsRequest: TextDocumentRequest, Hashable {
17+
public static let method: String = "document/tests"
18+
public typealias Response = [WorkspaceSymbolItem]?
19+
20+
public var textDocument: TextDocumentIdentifier
21+
22+
public init(textDocument: TextDocumentIdentifier) {
23+
self.textDocument = textDocument
24+
}
25+
}

Sources/SKCore/BuildSystemManager.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ extension BuildSystemManager: MainFilesDelegate {
268268
/// For Swift or normal C files, this will be the file itself. For header
269269
/// files, we pick a main file that includes the header since header files
270270
/// don't have build settings by themselves.
271-
private func mainFile(for uri: DocumentURI, language: Language, useCache: Bool = true) async -> DocumentURI {
271+
public func mainFile(for uri: DocumentURI, language: Language, useCache: Bool = true) async -> DocumentURI {
272272
if language == .swift {
273273
// Swift doesn't have main files. Skip the main file provider query.
274274
return uri

Sources/SourceKitLSP/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ add_library(SourceKitLSP STATIC
77
Sequence+AsyncMap.swift
88
SourceKitIndexDelegate.swift
99
SourceKitLSPCommandMetadata.swift
10-
SourceKitServer+Options.swift
1110
SourceKitServer.swift
11+
SourceKitServer+Options.swift
12+
TestDiscovery.swift
1213
ToolchainLanguageServer.swift
1314
Workspace.swift
1415
)

Sources/SourceKitLSP/SourceKitServer.swift

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ public actor SourceKitServer {
384384

385385
var languageServices: [LanguageServerType: [ToolchainLanguageServer]] = [:]
386386

387-
private let documentManager = DocumentManager()
387+
let documentManager = DocumentManager()
388388

389389
private var packageLoadingWorkDoneProgress = WorkDoneProgressState(
390390
"SourceKitLSP.SourceKitServer.reloadPackage",
@@ -400,7 +400,7 @@ public actor SourceKitServer {
400400
/// Must only be accessed from `queue`.
401401
private var uriToWorkspaceCache: [DocumentURI: WeakWorkspace] = [:]
402402

403-
private var workspaces: [Workspace] = [] {
403+
private(set) var workspaces: [Workspace] = [] {
404404
didSet {
405405
uriToWorkspaceCache = [:]
406406
}
@@ -841,6 +841,8 @@ extension SourceKitServer: MessageHandler {
841841
await request.reply { try await workspaceSymbols(request.params) }
842842
case let request as RequestAndReply<WorkspaceTestsRequest>:
843843
await request.reply { try await workspaceTests(request.params) }
844+
case let request as RequestAndReply<DocumentTestsRequest>:
845+
await self.handleRequest(for: request, requestHandler: self.documentTests)
844846
case let request as RequestAndReply<PollIndexRequest>:
845847
await request.reply { try await pollIndex(request.params) }
846848
case let request as RequestAndReply<BarrierRequest>:
@@ -1534,16 +1536,6 @@ extension SourceKitServer {
15341536
return symbols
15351537
}
15361538

1537-
func workspaceTests(_ req: WorkspaceTestsRequest) async throws -> [WorkspaceSymbolItem]? {
1538-
let testSymbols = workspaces.flatMap { (workspace) -> [SymbolOccurrence] in
1539-
guard let index = workspace.index else {
1540-
return []
1541-
}
1542-
return index.unitTests()
1543-
}
1544-
return testSymbols.map(WorkspaceSymbolItem.init)
1545-
}
1546-
15471539
/// Forwards a SymbolInfoRequest to the appropriate toolchain service for this document.
15481540
func symbolInfo(
15491541
_ req: SymbolInfoRequest,
@@ -2272,7 +2264,7 @@ fileprivate func transitiveSubtypeClosure(ofUsrs usrs: [String], index: IndexSto
22722264
return result
22732265
}
22742266

2275-
fileprivate extension WorkspaceSymbolItem {
2267+
extension WorkspaceSymbolItem {
22762268
init(_ symbolOccurrence: SymbolOccurrence) {
22772269
let symbolPosition = Position(
22782270
line: symbolOccurrence.location.line - 1, // 1-based -> 0-based
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import IndexStoreDB
14+
import LanguageServerProtocol
15+
16+
fileprivate extension SymbolOccurrence {
17+
/// Assuming that this is a symbol occurrence returned by the index, return whether it can constitute the definition
18+
/// of a test case.
19+
///
20+
/// The primary intention for this is to filter out references to test cases and extension declarations of test cases.
21+
/// The latter is important to filter so we don't include extension declarations for the derived `DiscoveredTests`
22+
/// files on non-Darwin platforms.
23+
var canBeTestDefinition: Bool {
24+
guard roles.contains(.definition) else {
25+
return false
26+
}
27+
guard symbol.kind == .class || symbol.kind == .instanceMethod else {
28+
return false
29+
}
30+
return true
31+
}
32+
}
33+
34+
extension SourceKitServer {
35+
func workspaceTests(_ req: WorkspaceTestsRequest) async throws -> [WorkspaceSymbolItem]? {
36+
let testSymbols = workspaces.flatMap { (workspace) -> [SymbolOccurrence] in
37+
return workspace.index?.unitTests() ?? []
38+
}
39+
return
40+
testSymbols
41+
.filter { $0.canBeTestDefinition }
42+
.map(WorkspaceSymbolItem.init)
43+
}
44+
45+
func documentTests(
46+
_ req: DocumentTestsRequest,
47+
workspace: Workspace,
48+
languageService: ToolchainLanguageServer
49+
) async throws -> [WorkspaceSymbolItem]? {
50+
let snapshot = try self.documentManager.latestSnapshot(req.textDocument.uri)
51+
let mainFileUri = await workspace.buildSystemManager.mainFile(
52+
for: req.textDocument.uri,
53+
language: snapshot.language
54+
)
55+
let testSymbols = workspace.index?.unitTests(referencedByMainFiles: [mainFileUri.pseudoPath]) ?? []
56+
return
57+
testSymbols
58+
.filter { $0.canBeTestDefinition }
59+
.map(WorkspaceSymbolItem.init)
60+
}
61+
}

Tests/SourceKitLSPTests/WorkspaceTestsTests.swift renamed to Tests/SourceKitLSPTests/TestDiscoveryTests.swift

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import LanguageServerProtocol
1414
import SKTestSupport
1515
import XCTest
1616

17-
final class WorkspaceTestsTests: XCTestCase {
17+
final class TestDiscoveryTests: XCTestCase {
1818
func testWorkspaceTests() async throws {
1919
try XCTSkipIf(longTestsDisabled)
2020

@@ -71,4 +71,69 @@ final class WorkspaceTestsTests: XCTestCase {
7171
]
7272
)
7373
}
74+
75+
func testDocumentTests() async throws {
76+
try XCTSkipIf(longTestsDisabled)
77+
78+
let ws = try await SwiftPMTestWorkspace(
79+
files: [
80+
"Tests/MyLibraryTests/MyTests.swift": """
81+
import XCTest
82+
83+
class 1️⃣MyTests: XCTestCase {
84+
func 2️⃣testMyLibrary() {}
85+
func unrelatedFunc() {}
86+
var testVariable: Int = 0
87+
}
88+
""",
89+
"Tests/MyLibraryTests/MoreTests.swift": """
90+
import XCTest
91+
92+
class MoreTests: XCTestCase {
93+
func testSomeMore() {}
94+
}
95+
""",
96+
],
97+
manifest: """
98+
// swift-tools-version: 5.7
99+
100+
import PackageDescription
101+
102+
let package = Package(
103+
name: "MyLibrary",
104+
targets: [.testTarget(name: "MyLibraryTests")]
105+
)
106+
""",
107+
build: true
108+
)
109+
110+
let (uri, positions) = try ws.openDocument("MyTests.swift")
111+
let tests = try await ws.testClient.send(DocumentTestsRequest(textDocument: TextDocumentIdentifier(uri)))
112+
XCTAssertEqual(
113+
tests,
114+
[
115+
WorkspaceSymbolItem.symbolInformation(
116+
SymbolInformation(
117+
name: "MyTests",
118+
kind: .class,
119+
location: Location(
120+
uri: uri,
121+
range: Range(positions["1️⃣"])
122+
)
123+
)
124+
),
125+
WorkspaceSymbolItem.symbolInformation(
126+
SymbolInformation(
127+
name: "testMyLibrary()",
128+
kind: .method,
129+
location: Location(
130+
uri: try ws.uri(for: "MyTests.swift"),
131+
range: Range(positions["2️⃣"])
132+
),
133+
containerName: "MyTests"
134+
)
135+
),
136+
]
137+
)
138+
}
74139
}

0 commit comments

Comments
 (0)