Skip to content

Open documents using testClient.openDocument instead of constructing a DidOpenDocumentNotification #910

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions Sources/SKTestSupport/LocationMarkers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

/// Finds all marked ranges in the given text, see `Marker`.
fileprivate func findMarkedRanges(text: String) -> [Marker] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked, is this basically just a copy paste from swift-syntax 😅?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a copy-paste from swift-syntax 😢

var markers = [Marker]()
while let marker = nextMarkedRange(text: text, from: markers.last?.range.upperBound ?? text.startIndex) {
markers.append(marker)
}
return markers
}

extension Character {
var isMarkerEmoji: Bool {
switch self {
case "0️⃣", "1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟", "ℹ️":
return true
default: return false
}
}
}

fileprivate func nextMarkedRange(text: String, from: String.Index) -> Marker? {
guard let start = text[from...].firstIndex(where: { $0.isMarkerEmoji }) else {
return nil
}
let end = text.index(after: start)

let markerRange = start..<end
let name = text[start..<end]

return Marker(name: name, range: markerRange)
}

fileprivate struct Marker {
/// The name of the marker.
let name: Substring
/// The range of the marker.
///
/// If the marker contains all the non-whitespace characters on the line,
/// this is the range of the entire line. Otherwise it's the range of the
/// marker itself.
let range: Range<String.Index>
}

public func extractMarkers(_ markedText: String) -> (markers: [String: Int], textWithoutMarkers: String) {
var text = ""
var markers = [String: Int]()
var lastIndex = markedText.startIndex
for marker in findMarkedRanges(text: markedText) {
text += markedText[lastIndex..<marker.range.lowerBound]
lastIndex = marker.range.upperBound

assert(markers[String(marker.name)] == nil, "Marker names must be unique")
markers[String(marker.name)] = text.utf8.count
}
text += markedText[lastIndex..<markedText.endIndex]

return (markers, text)
}
13 changes: 4 additions & 9 deletions Sources/SKTestSupport/SKSwiftPMTestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,10 @@ extension SKSwiftPMTestWorkspace {

extension SKSwiftPMTestWorkspace {
public func openDocument(_ url: URL, language: Language) throws {
testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: DocumentURI(url),
language: language,
version: 1,
text: try sources.sourceCache.get(url)
)
)
testClient.openDocument(
try sources.sourceCache.get(url),
uri: DocumentURI(url),
language: language
)
}

Expand Down
13 changes: 4 additions & 9 deletions Sources/SKTestSupport/SKTibsTestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,10 @@ extension SKTibsTestWorkspace {

extension SKTibsTestWorkspace {
public func openDocument(_ url: URL, language: Language) throws {
testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: DocumentURI(url),
language: language,
version: 1,
text: try sources.sourceCache.get(url)
)
)
testClient.openDocument(
try sources.sourceCache.get(url),
uri: DocumentURI(url),
language: language
)
}
}
Expand Down
77 changes: 77 additions & 0 deletions Sources/SKTestSupport/TestSourceKitLSPClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import LanguageServerProtocolJSONRPC
import SKCore
import SKSupport
import SourceKitLSP
import SwiftSyntax
import XCTest

extension SourceKitServer.Options {
Expand Down Expand Up @@ -216,6 +217,82 @@ public final class TestSourceKitLSPClient: MessageHandler {
reply(.success(requestHandler(params)))
requestHandlers.removeFirst()
}

// MARK: - Convenience functions

/// Opens the document with the given text as the given URI.
///
/// The version defaults to 0 and the language is inferred from the file's extension by default.
///
/// If the text contained location markers like `1️⃣`, then these are stripped from the opened document and
/// `DocumentPositions` are returned that map these markers to their position in the source file.
@discardableResult
public func openDocument(
_ markedText: String,
uri: DocumentURI,
version: Int = 0,
language: Language? = nil
) -> DocumentPositions {
let (markers, textWithoutMarkers) = extractMarkers(markedText)
var language = language
if language == nil {
guard let fileExtension = uri.fileURL?.pathExtension,
let inferredLanguage = Language(fileExtension: fileExtension)
else {
preconditionFailure("Unable to infer language for file \(uri)")
}
language = inferredLanguage
}

self.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: uri,
language: language!,
version: version,
text: textWithoutMarkers
)
)
)

return DocumentPositions(markers: markers, textWithoutMarkers: textWithoutMarkers)
}
}

// MARK: - DocumentPositions

/// Maps location marker like `1️⃣` to their position within a source file.
public struct DocumentPositions {
private let positions: [String: Position]

fileprivate init(markers: [String: Int], textWithoutMarkers: String) {
if markers.isEmpty {
// No need to build a line table if we don't have any markers.
positions = [:]
return
}

let lineTable = LineTable(textWithoutMarkers)
positions = markers.mapValues { offset in
guard let (line, column) = lineTable.lineAndUTF16ColumnOf(utf8Offset: offset) else {
preconditionFailure("UTF-8 offset not within source file: \(offset)")
}
return Position(line: line, utf16index: column)
}
}

fileprivate init(positions: [String: Position]) {
self.positions = positions
}

/// Returns the position of the given marker and traps if the document from which these `DocumentPositions` were
/// derived didn't contain the marker.
public subscript(_ marker: String) -> Position {
guard let position = positions[marker] else {
preconditionFailure("Could not find marker '\(marker)' in source code")
}
return position
}
}

// MARK: - WeakMessageHelper
Expand Down
44 changes: 44 additions & 0 deletions Sources/SKTestSupport/Utils.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation
import LanguageServerProtocol

extension Language {
var fileExtension: String {
switch self {
case .objective_c: return "m"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

objective_cpp could probably use a value here as well, though I assume we don't have tests with it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wanted to add a case here once/if we ever have an Objective-C++ test case.

default: return self.rawValue
}
}

init?(fileExtension: String) {
switch fileExtension {
case "m": self = .objective_c
default: self.init(rawValue: fileExtension)
}
}
}

extension DocumentURI {
/// Create a unique URI for a document of the given language.
public static func `for`(_ language: Language, testName: String = #function) -> DocumentURI {
let testBaseName = testName.prefix(while: \.isLetter)

#if os(Windows)
let url = URL(fileURLWithPath: "C:/\(testBaseName)/\(UUID())/test.\(language.fileExtension)")
#else
let url = URL(fileURLWithPath: "/\(testBaseName)/\(UUID())/test.\(language.fileExtension)")
#endif
return DocumentURI(url)
}
}
69 changes: 11 additions & 58 deletions Tests/SourceKitLSPTests/BuildSystemTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,8 @@ final class BuildSystemTests: XCTestCase {

guard haveClangd else { return }

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

let documentManager = await self.testClient.server._documentManager

testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: doc,
language: .objective_c,
version: 12,
text: text
)
)
)
testClient.openDocument(text, uri: doc)

let diags = try await testClient.nextDiagnosticsNotification()
XCTAssertEqual(diags.diagnostics.count, 1)
XCTAssertEqual(text, documentManager.latestSnapshot(doc)!.text)
Expand All @@ -191,8 +178,7 @@ final class BuildSystemTests: XCTestCase {
}

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

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

let documentManager = await self.testClient.server._documentManager

testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: doc,
language: .swift,
version: 12,
text: text
)
)
)
testClient.openDocument(text, uri: doc)
let diags1 = try await testClient.nextDiagnosticsNotification()
XCTAssertEqual(diags1.diagnostics.count, 1)
XCTAssertEqual(text, documentManager.latestSnapshot(doc)!.text)
Expand All @@ -236,13 +213,8 @@ final class BuildSystemTests: XCTestCase {
func testClangdDocumentFallbackWithholdsDiagnostics() async throws {
try XCTSkipIf(!haveClangd)

#if os(Windows)
let url = URL(fileURLWithPath: "C:/\(UUID())/file.m")
#else
let url = URL(fileURLWithPath: "/\(UUID())/file.m")
#endif
let doc = DocumentURI(url)
let args = [url.path, "-DDEBUG"]
let doc = DocumentURI.for(.objective_c)
let args = [doc.pseudoPath, "-DDEBUG"]
let text = """
#ifdef FOO
static void foo() {}
Expand All @@ -256,16 +228,7 @@ final class BuildSystemTests: XCTestCase {

let documentManager = await self.testClient.server._documentManager

testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: doc,
language: .objective_c,
version: 12,
text: text
)
)
)
testClient.openDocument(text, uri: doc)
let openDiags = try await testClient.nextDiagnosticsNotification()
// Expect diagnostics to be withheld.
XCTAssertEqual(openDiags.diagnostics.count, 0)
Expand All @@ -284,8 +247,7 @@ final class BuildSystemTests: XCTestCase {
}

func testSwiftDocumentFallbackWithholdsSemanticDiagnostics() async throws {
let url = URL(fileURLWithPath: "/\(UUID())/a.swift")
let doc = DocumentURI(url)
let doc = DocumentURI.for(.swift)

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

let documentManager = await self.testClient.server._documentManager

testClient.send(
DidOpenTextDocumentNotification(
textDocument: TextDocumentItem(
uri: doc,
language: .swift,
version: 12,
text: text
)
)
)
testClient.openDocument(text, uri: doc)
let openDiags = try await testClient.nextDiagnosticsNotification()
XCTAssertEqual(openDiags.diagnostics.count, 1)
XCTAssertEqual(text, documentManager.latestSnapshot(doc)!.text)
Expand Down
Loading