Skip to content

Commit ea6b837

Browse files
committed
Open documents using testClient.openDocument instead of constructing a DidOpenDocumentNotification
This removes a lot of boilerplate and makes the tests easier to read.
1 parent 4086874 commit ea6b837

16 files changed

+535
-703
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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+
/// Finds all marked ranges in the given text, see `Marker`.
14+
fileprivate func findMarkedRanges(text: String) -> [Marker] {
15+
var markers = [Marker]()
16+
while let marker = nextMarkedRange(text: text, from: markers.last?.range.upperBound ?? text.startIndex) {
17+
markers.append(marker)
18+
}
19+
return markers
20+
}
21+
22+
extension Character {
23+
var isMarkerEmoji: Bool {
24+
switch self {
25+
case "0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "ℹ️":
26+
return true
27+
default: return false
28+
}
29+
}
30+
}
31+
32+
fileprivate func nextMarkedRange(text: String, from: String.Index) -> Marker? {
33+
guard let start = text[from...].firstIndex(where: { $0.isMarkerEmoji }) else {
34+
return nil
35+
}
36+
let end = text.index(after: start)
37+
38+
let markerRange = start..<end
39+
let name = text[start..<end]
40+
41+
return Marker(name: name, range: markerRange)
42+
}
43+
44+
fileprivate struct Marker {
45+
/// The name of the marker.
46+
let name: Substring
47+
/// The range of the marker.
48+
///
49+
/// If the marker contains all the non-whitespace characters on the line,
50+
/// this is the range of the entire line. Otherwise it's the range of the
51+
/// marker itself.
52+
let range: Range<String.Index>
53+
}
54+
55+
public func extractMarkers(_ markedText: String) -> (markers: [String: Int], textWithoutMarkers: String) {
56+
var text = ""
57+
var markers = [String: Int]()
58+
var lastIndex = markedText.startIndex
59+
for marker in findMarkedRanges(text: markedText) {
60+
text += markedText[lastIndex..<marker.range.lowerBound]
61+
lastIndex = marker.range.upperBound
62+
63+
assert(markers[String(marker.name)] == nil, "Marker names must be unique")
64+
markers[String(marker.name)] = text.utf8.count
65+
}
66+
text += markedText[lastIndex..<markedText.endIndex]
67+
68+
return (markers, text)
69+
}

Sources/SKTestSupport/SKSwiftPMTestWorkspace.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,10 @@ extension SKSwiftPMTestWorkspace {
156156

157157
extension SKSwiftPMTestWorkspace {
158158
public func openDocument(_ url: URL, language: Language) throws {
159-
testClient.send(
160-
DidOpenTextDocumentNotification(
161-
textDocument: TextDocumentItem(
162-
uri: DocumentURI(url),
163-
language: language,
164-
version: 1,
165-
text: try sources.sourceCache.get(url)
166-
)
167-
)
159+
testClient.openDocument(
160+
try sources.sourceCache.get(url),
161+
uri: DocumentURI(url),
162+
language: language
168163
)
169164
}
170165

Sources/SKTestSupport/SKTibsTestWorkspace.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,10 @@ extension SKTibsTestWorkspace {
122122

123123
extension SKTibsTestWorkspace {
124124
public func openDocument(_ url: URL, language: Language) throws {
125-
testClient.send(
126-
DidOpenTextDocumentNotification(
127-
textDocument: TextDocumentItem(
128-
uri: DocumentURI(url),
129-
language: language,
130-
version: 1,
131-
text: try sources.sourceCache.get(url)
132-
)
133-
)
125+
testClient.openDocument(
126+
try sources.sourceCache.get(url),
127+
uri: DocumentURI(url),
128+
language: language
134129
)
135130
}
136131
}

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import LanguageServerProtocolJSONRPC
1717
import SKCore
1818
import SKSupport
1919
import SourceKitLSP
20+
import SwiftSyntax
2021
import XCTest
2122

2223
extension SourceKitServer.Options {
@@ -216,6 +217,82 @@ public final class TestSourceKitLSPClient: MessageHandler {
216217
reply(.success(requestHandler(params)))
217218
requestHandlers.removeFirst()
218219
}
220+
221+
// MARK: - Convenience functions
222+
223+
/// Opens the document with the given text as the given URI.
224+
///
225+
/// The version defaults to 0 and the language is inferred from the file's extension by default.
226+
///
227+
/// If the text contained location markers like `1️⃣`, then these are stripped from the opened document and
228+
/// `DocumentPositions` are returned that map these markers to their position in the source file.
229+
@discardableResult
230+
public func openDocument(
231+
_ markedText: String,
232+
uri: DocumentURI,
233+
version: Int = 0,
234+
language: Language? = nil
235+
) -> DocumentPositions {
236+
let (markers, textWithoutMarkers) = extractMarkers(markedText)
237+
var language = language
238+
if language == nil {
239+
guard let fileExtension = uri.fileURL?.pathExtension,
240+
let inferredLanguage = Language(fileExtension: fileExtension)
241+
else {
242+
preconditionFailure("Unable to infer language for file \(uri)")
243+
}
244+
language = inferredLanguage
245+
}
246+
247+
self.send(
248+
DidOpenTextDocumentNotification(
249+
textDocument: TextDocumentItem(
250+
uri: uri,
251+
language: language!,
252+
version: version,
253+
text: textWithoutMarkers
254+
)
255+
)
256+
)
257+
258+
return DocumentPositions(markers: markers, textWithoutMarkers: textWithoutMarkers)
259+
}
260+
}
261+
262+
// MARK: - DocumentPositions
263+
264+
/// Maps location marker like `1️⃣` to their position within a source file.
265+
public struct DocumentPositions {
266+
private let positions: [String: Position]
267+
268+
fileprivate init(markers: [String: Int], textWithoutMarkers: String) {
269+
if markers.isEmpty {
270+
// No need to build a line table if we don't have any markers.
271+
positions = [:]
272+
return
273+
}
274+
275+
let lineTable = LineTable(textWithoutMarkers)
276+
positions = markers.mapValues { offset in
277+
guard let (line, column) = lineTable.lineAndUTF16ColumnOf(utf8Offset: offset) else {
278+
preconditionFailure("UTF-8 offset not within source file: \(offset)")
279+
}
280+
return Position(line: line, utf16index: column)
281+
}
282+
}
283+
284+
fileprivate init(positions: [String: Position]) {
285+
self.positions = positions
286+
}
287+
288+
/// Returns the position of the given marker and traps if the document from which these `DocumentPositions` were
289+
/// derived didn't contain the marker.
290+
public subscript(_ marker: String) -> Position {
291+
guard let position = positions[marker] else {
292+
preconditionFailure("Could not find marker '\(marker)' in source code")
293+
}
294+
return position
295+
}
219296
}
220297

221298
// MARK: - WeakMessageHelper

Sources/SKTestSupport/Utils.swift

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 Foundation
14+
import LanguageServerProtocol
15+
16+
extension Language {
17+
var fileExtension: String {
18+
switch self {
19+
case .objective_c: return "m"
20+
default: return self.rawValue
21+
}
22+
}
23+
24+
init?(fileExtension: String) {
25+
switch fileExtension {
26+
case "m": self = .objective_c
27+
default: self.init(rawValue: fileExtension)
28+
}
29+
}
30+
}
31+
32+
extension DocumentURI {
33+
/// Create a unique URI for a document of the given language.
34+
public static func `for`(_ language: Language, testName: String = #function) -> DocumentURI {
35+
let testBaseName = testName.prefix(while: \.isLetter)
36+
37+
#if os(Windows)
38+
let url = URL(fileURLWithPath: "C:/\(testBaseName)/\(UUID())/test.\(language.fileExtension)")
39+
#else
40+
let url = URL(fileURLWithPath: "/\(testBaseName)/\(UUID())/test.\(language.fileExtension)")
41+
#endif
42+
return DocumentURI(url)
43+
}
44+
}

Tests/SourceKitLSPTests/BuildSystemTests.swift

Lines changed: 11 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,8 @@ final class BuildSystemTests: XCTestCase {
139139

140140
guard haveClangd else { return }
141141

142-
#if os(Windows)
143-
let url = URL(fileURLWithPath: "C:/\(UUID())/file.m")
144-
#else
145-
let url = URL(fileURLWithPath: "/\(UUID())/file.m")
146-
#endif
147-
let doc = DocumentURI(url)
148-
let args = [url.path, "-DDEBUG"]
142+
let doc = DocumentURI.for(.objective_c)
143+
let args = [doc.pseudoPath, "-DDEBUG"]
149144
let text = """
150145
#ifdef FOO
151146
static void foo() {}
@@ -161,16 +156,8 @@ final class BuildSystemTests: XCTestCase {
161156

162157
let documentManager = await self.testClient.server._documentManager
163158

164-
testClient.send(
165-
DidOpenTextDocumentNotification(
166-
textDocument: TextDocumentItem(
167-
uri: doc,
168-
language: .objective_c,
169-
version: 12,
170-
text: text
171-
)
172-
)
173-
)
159+
testClient.openDocument(text, uri: doc)
160+
174161
let diags = try await testClient.nextDiagnosticsNotification()
175162
XCTAssertEqual(diags.diagnostics.count, 1)
176163
XCTAssertEqual(text, documentManager.latestSnapshot(doc)!.text)
@@ -191,8 +178,7 @@ final class BuildSystemTests: XCTestCase {
191178
}
192179

193180
func testSwiftDocumentUpdatedBuildSettings() async throws {
194-
let url = URL(fileURLWithPath: "/\(UUID())/a.swift")
195-
let doc = DocumentURI(url)
181+
let doc = DocumentURI.for(.swift)
196182
let args = FallbackBuildSystem(buildSetup: .default).buildSettings(for: doc, language: .swift)!.compilerArguments
197183

198184
buildSystem.buildSettingsByFile[doc] = FileBuildSettings(compilerArguments: args)
@@ -207,16 +193,7 @@ final class BuildSystemTests: XCTestCase {
207193

208194
let documentManager = await self.testClient.server._documentManager
209195

210-
testClient.send(
211-
DidOpenTextDocumentNotification(
212-
textDocument: TextDocumentItem(
213-
uri: doc,
214-
language: .swift,
215-
version: 12,
216-
text: text
217-
)
218-
)
219-
)
196+
testClient.openDocument(text, uri: doc)
220197
let diags1 = try await testClient.nextDiagnosticsNotification()
221198
XCTAssertEqual(diags1.diagnostics.count, 1)
222199
XCTAssertEqual(text, documentManager.latestSnapshot(doc)!.text)
@@ -236,13 +213,8 @@ final class BuildSystemTests: XCTestCase {
236213
func testClangdDocumentFallbackWithholdsDiagnostics() async throws {
237214
try XCTSkipIf(!haveClangd)
238215

239-
#if os(Windows)
240-
let url = URL(fileURLWithPath: "C:/\(UUID())/file.m")
241-
#else
242-
let url = URL(fileURLWithPath: "/\(UUID())/file.m")
243-
#endif
244-
let doc = DocumentURI(url)
245-
let args = [url.path, "-DDEBUG"]
216+
let doc = DocumentURI.for(.objective_c)
217+
let args = [doc.pseudoPath, "-DDEBUG"]
246218
let text = """
247219
#ifdef FOO
248220
static void foo() {}
@@ -256,16 +228,7 @@ final class BuildSystemTests: XCTestCase {
256228

257229
let documentManager = await self.testClient.server._documentManager
258230

259-
testClient.send(
260-
DidOpenTextDocumentNotification(
261-
textDocument: TextDocumentItem(
262-
uri: doc,
263-
language: .objective_c,
264-
version: 12,
265-
text: text
266-
)
267-
)
268-
)
231+
testClient.openDocument(text, uri: doc)
269232
let openDiags = try await testClient.nextDiagnosticsNotification()
270233
// Expect diagnostics to be withheld.
271234
XCTAssertEqual(openDiags.diagnostics.count, 0)
@@ -284,8 +247,7 @@ final class BuildSystemTests: XCTestCase {
284247
}
285248

286249
func testSwiftDocumentFallbackWithholdsSemanticDiagnostics() async throws {
287-
let url = URL(fileURLWithPath: "/\(UUID())/a.swift")
288-
let doc = DocumentURI(url)
250+
let doc = DocumentURI.for(.swift)
289251

290252
// Primary settings must be different than the fallback settings.
291253
var primarySettings = FallbackBuildSystem(buildSetup: .default).buildSettings(for: doc, language: .swift)!
@@ -302,16 +264,7 @@ final class BuildSystemTests: XCTestCase {
302264

303265
let documentManager = await self.testClient.server._documentManager
304266

305-
testClient.send(
306-
DidOpenTextDocumentNotification(
307-
textDocument: TextDocumentItem(
308-
uri: doc,
309-
language: .swift,
310-
version: 12,
311-
text: text
312-
)
313-
)
314-
)
267+
testClient.openDocument(text, uri: doc)
315268
let openDiags = try await testClient.nextDiagnosticsNotification()
316269
XCTAssertEqual(openDiags.diagnostics.count, 1)
317270
XCTAssertEqual(text, documentManager.latestSnapshot(doc)!.text)

0 commit comments

Comments
 (0)