mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
We weren’t sure if it works because it always sends 0 as the document’s version number, but everythig appears to be fine.
298 lines
11 KiB
Swift
298 lines
11 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2022 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
|
|
import LSPTestSupport
|
|
import SourceKitLSP
|
|
import SKCore
|
|
import SKTestSupport
|
|
import TSCBasic
|
|
import XCTest
|
|
|
|
final class WorkspaceTests: XCTestCase {
|
|
|
|
func testMultipleSwiftPMWorkspaces() throws {
|
|
guard let ws = try staticSourceKitSwiftPMWorkspace(name: "SwiftPMPackage") else { return }
|
|
try ws.buildAndIndex()
|
|
|
|
guard let otherWs = try staticSourceKitSwiftPMWorkspace(name: "OtherSwiftPMPackage", server: ws.testServer) else { return }
|
|
try otherWs.buildAndIndex()
|
|
|
|
assert(ws.testServer === otherWs.testServer, "Sanity check: The two workspaces should be opened in the same server")
|
|
|
|
let call = ws.testLoc("Lib.foo:call")
|
|
let otherCall = otherWs.testLoc("FancyLib.sayHello:call")
|
|
|
|
try ws.openDocument(call.url, language: .swift)
|
|
|
|
let completions = try withExtendedLifetime(ws) {
|
|
try ws.sk.sendSync(CompletionRequest(textDocument: call.docIdentifier, position: call.position))
|
|
}
|
|
|
|
XCTAssertEqual(completions.items, [
|
|
CompletionItem(
|
|
label: "foo()",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
sortText: nil,
|
|
filterText: "foo()",
|
|
insertText: "foo()",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(TextEdit(range: Position(line: 2, utf16index: 24)..<Position(line: 2, utf16index: 24), newText: "foo()"))),
|
|
CompletionItem(
|
|
label: "self",
|
|
kind: .keyword,
|
|
detail: "Lib",
|
|
deprecated: false,
|
|
sortText: nil,
|
|
filterText: "self",
|
|
insertText: "self",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(TextEdit(range: Position(line: 2, utf16index: 24)..<Position(line: 2, utf16index: 24), newText: "self"))),
|
|
])
|
|
|
|
try ws.openDocument(otherCall.url, language: .swift)
|
|
|
|
let otherCompletions = try withExtendedLifetime(ws) {
|
|
try ws.sk.sendSync(CompletionRequest(textDocument: otherCall.docIdentifier, position: otherCall.position))
|
|
}
|
|
|
|
XCTAssertEqual(otherCompletions.items, [
|
|
CompletionItem(
|
|
label: "sayHello()",
|
|
kind: .method,
|
|
detail: "Void",
|
|
documentation: nil,
|
|
deprecated: false,
|
|
sortText: nil,
|
|
filterText: "sayHello()",
|
|
insertText: "sayHello()",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(TextEdit(range: Position(line: 7, utf16index: 41)..<Position(line: 7, utf16index: 41), newText: "sayHello()"))
|
|
),
|
|
CompletionItem(
|
|
label: "self",
|
|
kind: LanguageServerProtocol.CompletionItemKind(rawValue: 14),
|
|
detail: "FancyLib",
|
|
documentation: nil,
|
|
deprecated: false,
|
|
sortText: nil,
|
|
filterText: "self",
|
|
insertText: "self",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(TextEdit(range: Position(line: 7, utf16index: 41)..<Position(line: 7, utf16index: 41), newText: "self"))
|
|
),
|
|
])
|
|
}
|
|
|
|
func testMultipleClangdWorkspaces() {
|
|
guard let ws = try! staticSourceKitTibsWorkspace(name: "ClangModules") else { return }
|
|
|
|
let loc = ws.testLoc("main_file")
|
|
|
|
let expectation = self.expectation(description: "diagnostics")
|
|
|
|
ws.sk.handleNextNotification { (note: Notification<PublishDiagnosticsNotification>) in
|
|
XCTAssertEqual(note.params.diagnostics.count, 0)
|
|
expectation.fulfill()
|
|
}
|
|
|
|
try! ws.openDocument(loc.url, language: .objective_c)
|
|
|
|
waitForExpectations(timeout: defaultTimeout)
|
|
|
|
let otherWs = try! staticSourceKitTibsWorkspace(name: "ClangCrashRecoveryBuildSettings", server: ws.testServer)!
|
|
assert(ws.testServer === otherWs.testServer, "Sanity check: The two workspaces should be opened in the same server")
|
|
let otherLoc = otherWs.testLoc("loc")
|
|
|
|
try! otherWs.openDocument(otherLoc.url, language: .cpp)
|
|
|
|
// Do a sanity check and verify that we get the expected result from a hover response before crashing clangd.
|
|
|
|
let expectedHighlightResponse = [
|
|
DocumentHighlight(range: Position(line: 3, utf16index: 5)..<Position(line: 3, utf16index: 8), kind: .text),
|
|
DocumentHighlight(range: Position(line: 9, utf16index: 2)..<Position(line: 9, utf16index: 5), kind: .text)
|
|
]
|
|
|
|
let highlightRequest = DocumentHighlightRequest(textDocument: otherLoc.docIdentifier, position: Position(line: 9, utf16index: 3))
|
|
let highlightResponse = try! otherWs.sk.sendSync(highlightRequest)
|
|
XCTAssertEqual(highlightResponse, expectedHighlightResponse)
|
|
}
|
|
|
|
func testRecomputeFileWorkspaceMembershipOnPackageSwiftChange() throws {
|
|
guard let otherWs = try staticSourceKitSwiftPMWorkspace(name: "OtherSwiftPMPackage") else { return }
|
|
try otherWs.buildAndIndex()
|
|
|
|
guard let ws = try staticSourceKitSwiftPMWorkspace(name: "SwiftPMPackage", server: otherWs.testServer) else { return }
|
|
try ws.buildAndIndex()
|
|
|
|
assert(ws.testServer === otherWs.testServer, "Sanity check: The two workspaces should be opened in the same server")
|
|
|
|
let otherLib = ws.testLoc("OtherLib.topLevelFunction:libMember")
|
|
let packageTargets = ws.testLoc("Package.swift:targets")
|
|
|
|
try ws.openDocument(otherLib.url, language: .swift)
|
|
|
|
// We open OtherSwiftPMPackage first. Thus, otherLib (which is a folder in
|
|
// SwiftPMPackage that hasn't been added to Package.swift yet) will belong
|
|
// to OtherSwiftPMPackage by default (because it provides fallback build
|
|
// settings for it).
|
|
XCTAssertEqual(ws.testServer.server!.workspaceForDocumentOnQueue(uri: otherLib.docUri)?.rootUri, DocumentURI(otherWs.sources.rootDirectory))
|
|
|
|
// Add the otherlib target to Package.swift
|
|
_ = try ws.sources.edit { builder in
|
|
let packageManifest = ws.sources.rootDirectory
|
|
.appendingPathComponent("Package.swift")
|
|
var packageManifestContents = try! String(contentsOf: packageManifest, encoding: .utf8)
|
|
let targetMarkerRange = packageManifestContents.range(of: "/*Package.swift:targets*/")!
|
|
packageManifestContents.replaceSubrange(targetMarkerRange, with: """
|
|
.target(
|
|
name: "otherlib",
|
|
dependencies: ["lib"]
|
|
),
|
|
/*Package.swift:targets*/
|
|
""")
|
|
builder.write(packageManifestContents, to: packageManifest)
|
|
}
|
|
|
|
ws.sk.send(DidChangeWatchedFilesNotification(changes: [
|
|
FileEvent(uri: packageTargets.docUri, type: .changed)
|
|
]))
|
|
|
|
// After updating Package.swift in SwiftPMPackage, SwiftPMPackage can
|
|
// provide proper build settings for otherLib and thus workspace
|
|
// membership should switch to SwiftPMPackage.
|
|
|
|
// Updating the build settings takes a few seconds. Send code completion requests every second until we receive correct results.
|
|
var didReceiveCorrectWorkspaceMembership = false
|
|
|
|
// Updating the build settings takes a few seconds. Send code completion requests every second until we receive correct results.
|
|
for _ in 0..<30 {
|
|
if ws.testServer.server!.workspaceForDocumentOnQueue(uri: otherLib.docUri)?.rootUri == DocumentURI(ws.sources.rootDirectory) {
|
|
didReceiveCorrectWorkspaceMembership = true
|
|
break
|
|
}
|
|
Thread.sleep(forTimeInterval: 1)
|
|
}
|
|
|
|
XCTAssert(didReceiveCorrectWorkspaceMembership)
|
|
}
|
|
|
|
func testMixedPackage() throws {
|
|
guard let ws = try staticSourceKitSwiftPMWorkspace(name: "MixedPackage") else { return }
|
|
try ws.buildAndIndex()
|
|
|
|
let cLoc = ws.testLoc("clib_func:body")
|
|
let swiftLoc = ws.testLoc("lib.swift:toplevel")
|
|
|
|
try ws.openDocument(swiftLoc.url, language: .swift)
|
|
try ws.openDocument(cLoc.url, language: .c)
|
|
|
|
let receivedResponse = self.expectation(description: "Received completion response")
|
|
|
|
_ = ws.sk.send(CompletionRequest(textDocument: cLoc.docIdentifier, position: cLoc.position)) { result in
|
|
defer {
|
|
receivedResponse.fulfill()
|
|
}
|
|
guard case .success(_) = result else {
|
|
XCTFail("Expected a successful response")
|
|
return
|
|
}
|
|
}
|
|
|
|
self.wait(for: [receivedResponse], timeout: defaultTimeout)
|
|
}
|
|
|
|
func testChangeWorkspaceFolders() throws {
|
|
guard let ws = try staticSourceKitSwiftPMWorkspace(name: "ChangeWorkspaceFolders") else { return }
|
|
// Build the package. We can't use ws.buildAndIndex() because that doesn't put the build products in .build where SourceKit-LSP expects them.
|
|
try TSCBasic.Process.checkNonZeroExit(arguments: [
|
|
ToolchainRegistry.shared.default!.swift!.pathString,
|
|
"build",
|
|
"--package-path", ws.sources.rootDirectory.path,
|
|
"-Xswiftc", "-index-ignore-system-modules",
|
|
"-Xcc", "-index-ignore-system-symbols",
|
|
])
|
|
|
|
let otherPackLoc = ws.testLoc("otherPackage:call")
|
|
|
|
let testServer = TestSourceKitServer(connectionKind: .local)
|
|
let sk = testServer.client
|
|
_ = try sk.sendSync(InitializeRequest(
|
|
rootURI: nil,
|
|
capabilities: ClientCapabilities(workspace: .init(workspaceFolders: true)),
|
|
workspaceFolders: [
|
|
WorkspaceFolder(uri: DocumentURI(ws.sources.rootDirectory.deletingLastPathComponent()))
|
|
]
|
|
))
|
|
|
|
let docString = try String(data: Data(contentsOf: otherPackLoc.url), encoding: .utf8)!
|
|
|
|
sk.send(DidOpenTextDocumentNotification(
|
|
textDocument: TextDocumentItem(
|
|
uri: otherPackLoc.docUri,
|
|
language: .swift,
|
|
version: 1,
|
|
text: docString)
|
|
))
|
|
|
|
let preChangeWorkspaceResponse = try sk.sendSync(CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(otherPackLoc.docUri),
|
|
position: otherPackLoc.position
|
|
))
|
|
|
|
XCTAssertEqual(preChangeWorkspaceResponse.items, [], "Did not expect to receive cross-module code completion results if we opened the parent directory of the package")
|
|
|
|
sk.send(DidChangeWorkspaceFoldersNotification(event: WorkspaceFoldersChangeEvent(added: [
|
|
WorkspaceFolder(uri: DocumentURI(ws.sources.rootDirectory))
|
|
])))
|
|
|
|
let postChangeWorkspaceResponse = try sk.sendSync(CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(otherPackLoc.docUri),
|
|
position: otherPackLoc.position
|
|
))
|
|
|
|
XCTAssertEqual(postChangeWorkspaceResponse.items, [
|
|
CompletionItem(
|
|
label: "helloWorld()",
|
|
kind: .method,
|
|
detail: "Void",
|
|
documentation: nil,
|
|
deprecated: false, sortText: nil,
|
|
filterText: "helloWorld()",
|
|
insertText: "helloWorld()",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(TextEdit(
|
|
range: otherPackLoc.position..<otherPackLoc.position,
|
|
newText: "helloWorld()"
|
|
))
|
|
),
|
|
CompletionItem(
|
|
label: "self",
|
|
kind: .keyword,
|
|
detail: "Package",
|
|
documentation: nil,
|
|
deprecated: false, sortText: nil,
|
|
filterText: "self",
|
|
insertText: "self",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(TextEdit(
|
|
range: otherPackLoc.position..<otherPackLoc.position,
|
|
newText: "self"
|
|
))
|
|
)
|
|
])
|
|
}
|
|
}
|