mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
Add `.swift-format` to the repo and format the repo with `swift-format`. This commit does not add any automation to enforce formatting of sourcekit-lsp in CI. The goal of this commit is to get the majority of source changes out of the way so that the diff of actually enforcing formatting will have fewer changes or conflicts.
490 lines
13 KiB
Swift
490 lines
13 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2018 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 LSPTestSupport
|
|
import LanguageServerProtocol
|
|
import LanguageServerProtocolJSONRPC
|
|
import XCTest
|
|
|
|
final class CodingTests: XCTestCase {
|
|
|
|
func testMessageCoding() {
|
|
checkMessageCoding(
|
|
InitializeRequest(
|
|
processId: 1,
|
|
clientInfo: InitializeRequest.ClientInfo(name: "dummy-client", version: "1.0"),
|
|
locale: "en-US",
|
|
rootPath: "/foo",
|
|
rootURI: nil,
|
|
initializationOptions: nil,
|
|
capabilities: ClientCapabilities(workspace: nil, textDocument: nil),
|
|
trace: .off,
|
|
workspaceFolders: nil
|
|
),
|
|
id: .number(2),
|
|
json: """
|
|
{
|
|
"id" : 2,
|
|
"jsonrpc" : "2.0",
|
|
"method" : "initialize",
|
|
"params" : {
|
|
"capabilities" : {
|
|
|
|
},
|
|
"clientInfo" : {
|
|
"name" : "dummy-client",
|
|
"version" : "1.0"
|
|
},
|
|
"locale" : "en-US",
|
|
"processId" : 1,
|
|
"rootPath" : "\\/foo",
|
|
"trace" : "off"
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
|
|
checkMessageCoding(
|
|
InitializeRequest(
|
|
processId: 1,
|
|
rootPath: "/foo",
|
|
rootURI: nil,
|
|
initializationOptions: nil,
|
|
capabilities: ClientCapabilities(workspace: nil, textDocument: nil),
|
|
trace: .off,
|
|
workspaceFolders: nil
|
|
),
|
|
id: .string("3"),
|
|
json: """
|
|
{
|
|
"id" : "3",
|
|
"jsonrpc" : "2.0",
|
|
"method" : "initialize",
|
|
"params" : {
|
|
"capabilities" : {
|
|
|
|
},
|
|
"processId" : 1,
|
|
"rootPath" : "\\/foo",
|
|
"trace" : "off"
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
|
|
checkMessageCoding(
|
|
CancelRequestNotification(id: .number(1)),
|
|
json: """
|
|
{
|
|
"jsonrpc" : "2.0",
|
|
"method" : "$\\/cancelRequest",
|
|
"params" : {
|
|
"id" : 1
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
|
|
checkMessageCoding(
|
|
InitializedNotification(),
|
|
json: """
|
|
{
|
|
"jsonrpc" : "2.0",
|
|
"method" : "initialized",
|
|
"params" : {
|
|
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
|
|
checkMessageCoding(
|
|
InitializeResult(
|
|
capabilities: ServerCapabilities(
|
|
textDocumentSync: .options(
|
|
TextDocumentSyncOptions(
|
|
openClose: true,
|
|
change: .incremental,
|
|
willSave: true,
|
|
willSaveWaitUntil: false,
|
|
save: .value(TextDocumentSyncOptions.SaveOptions(includeText: false))
|
|
)
|
|
),
|
|
completionProvider: CompletionOptions(
|
|
resolveProvider: false,
|
|
triggerCharacters: ["."]
|
|
)
|
|
)
|
|
),
|
|
id: .number(2),
|
|
json: """
|
|
{
|
|
"id" : 2,
|
|
"jsonrpc" : "2.0",
|
|
"result" : {
|
|
"capabilities" : {
|
|
"completionProvider" : {
|
|
"resolveProvider" : false,
|
|
"triggerCharacters" : [
|
|
"."
|
|
]
|
|
},
|
|
"textDocumentSync" : {
|
|
"change" : 2,
|
|
"openClose" : true,
|
|
"save" : {
|
|
"includeText" : false
|
|
},
|
|
"willSave" : true,
|
|
"willSaveWaitUntil" : false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
)
|
|
|
|
checkMessageCoding(
|
|
ResponseError.cancelled,
|
|
id: .number(2),
|
|
json: """
|
|
{
|
|
"error" : {
|
|
"code" : -32800,
|
|
"message" : "request cancelled by client"
|
|
},
|
|
"id" : 2,
|
|
"jsonrpc" : "2.0"
|
|
}
|
|
"""
|
|
)
|
|
|
|
checkMessageCoding(
|
|
ResponseError.methodNotFound("asdf"),
|
|
id: .number(2),
|
|
json: """
|
|
{
|
|
"error" : {
|
|
"code" : -32601,
|
|
"message" : "method not found: asdf"
|
|
},
|
|
"id" : 2,
|
|
"jsonrpc" : "2.0"
|
|
}
|
|
"""
|
|
)
|
|
|
|
checkMessageCoding(
|
|
ResponseError.cancelled,
|
|
id: nil,
|
|
json: """
|
|
{
|
|
"error" : {
|
|
"code" : -32800,
|
|
"message" : "request cancelled by client"
|
|
},
|
|
"id" : null,
|
|
"jsonrpc" : "2.0"
|
|
}
|
|
"""
|
|
)
|
|
}
|
|
|
|
func testMessageDecodingError() {
|
|
// Note: JSON parsing errors are caught at a higher level.
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidRequest("jsonrpc version must be 2.0"),
|
|
json: """
|
|
{}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidRequest("message not recognized as request, response or notification"),
|
|
json: """
|
|
{"jsonrpc":"2.0"}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidRequest("message not recognized as request, response or notification"),
|
|
json: """
|
|
{"jsonrpc":"2.0","params":{}}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidRequest("message not recognized as request, response or notification"),
|
|
json: """
|
|
{"jsonrpc":"2.0","result":{}}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.methodNotFound("unknown", messageKind: .notification),
|
|
json: """
|
|
{"jsonrpc":"2.0","method":"unknown"}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.methodNotFound("unknown", id: .number(2), messageKind: .request),
|
|
json: """
|
|
{"jsonrpc":"2.0","id":2,"method":"unknown"}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.methodNotFound("initialized", id: .number(2), messageKind: .request),
|
|
json: """
|
|
{"jsonrpc":"2.0","id":2,"method":"initialized"}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.methodNotFound("initialize", messageKind: .notification),
|
|
json: """
|
|
{"jsonrpc":"2.0","method":"initialize"}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidParams("missing expected parameter: params", messageKind: .notification),
|
|
json: """
|
|
{"jsonrpc":"2.0","method":"$/cancelRequest"}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidParams("type mismatch at params :", messageKind: .notification),
|
|
json: """
|
|
{"jsonrpc":"2.0","method":"$/cancelRequest","params":2}
|
|
"""
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidParams("missing expected parameter: id", messageKind: .notification),
|
|
json: """
|
|
{"jsonrpc":"2.0","method":"$/cancelRequest","params":{}}
|
|
"""
|
|
)
|
|
|
|
let responseTypeCallback: JSONRPCMessage.ResponseTypeCallback = {
|
|
return $0 == .string("unknown") ? nil : InitializeResult.self
|
|
}
|
|
|
|
let info = defaultCodingInfo.merging([CodingUserInfoKey.responseTypeCallbackKey: responseTypeCallback]) {
|
|
(_, new) in new
|
|
}
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidRequest(
|
|
"message not recognized as request, response or notification",
|
|
id: .number(2)
|
|
),
|
|
json: """
|
|
{"jsonrpc":"2.0","id":2,"params":{}}
|
|
""",
|
|
userInfo: info
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidRequest("message not recognized as request, response or notification"),
|
|
json: """
|
|
{"jsonrpc":"2.0","params":{}}
|
|
""",
|
|
userInfo: info
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidRequest(
|
|
"message not recognized as request, response or notification",
|
|
id: .string("3")
|
|
),
|
|
json: """
|
|
{"jsonrpc":"2.0","id":"3","params":{}}
|
|
""",
|
|
userInfo: info
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidRequest(
|
|
"message not recognized as request, response or notification",
|
|
id: .string("unknown")
|
|
),
|
|
json: """
|
|
{"jsonrpc":"2.0","id":"unknown","params":{}}
|
|
""",
|
|
userInfo: info
|
|
)
|
|
|
|
checkMessageDecodingError(
|
|
MessageDecodingError.invalidParams(
|
|
"missing expected parameter: capabilities",
|
|
id: .number(2),
|
|
messageKind: .response
|
|
),
|
|
json: """
|
|
{"jsonrpc":"2.0","id":2,"result":{}}
|
|
""",
|
|
userInfo: info
|
|
)
|
|
}
|
|
|
|
// SR-16095
|
|
func testDecodeShutdownWithoutParams() throws {
|
|
let json = """
|
|
{
|
|
"id" : 1,
|
|
"jsonrpc" : "2.0",
|
|
"method" : "shutdown"
|
|
}
|
|
"""
|
|
|
|
let decoder = JSONDecoder()
|
|
decoder.userInfo = defaultCodingInfo
|
|
let decodedValue = try decoder.decode(JSONRPCMessage.self, from: json.data(using: .utf8)!)
|
|
|
|
guard case JSONRPCMessage.request(let decodedValueOpaque, let decodedID) = decodedValue,
|
|
let decodedRequest = decodedValueOpaque as? ShutdownRequest
|
|
else {
|
|
XCTFail("decodedValue \(decodedValue) is not a ShutdownRequest")
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(.number(1), decodedID, "expected request ID 1")
|
|
XCTAssertEqual(ShutdownRequest(), decodedRequest)
|
|
}
|
|
}
|
|
|
|
let defaultCodingInfo: [CodingUserInfoKey: Any] = [CodingUserInfoKey.messageRegistryKey: MessageRegistry.lspProtocol]
|
|
|
|
private func checkMessageCoding<Request: RequestType & Equatable>(
|
|
_ value: Request,
|
|
id: RequestID,
|
|
json: String,
|
|
file: StaticString = #filePath,
|
|
line: UInt = #line
|
|
) {
|
|
checkCoding(JSONRPCMessage.request(value, id: id), json: json, userInfo: defaultCodingInfo, file: file, line: line) {
|
|
guard case JSONRPCMessage.request(let decodedValueOpaque, let decodedID) = $0,
|
|
let decodedValue = decodedValueOpaque as? Request
|
|
else {
|
|
XCTFail("decodedValue \($0) does not match expected \(value)", file: file, line: line)
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(id, decodedID, "requestID decoding", file: file, line: line)
|
|
XCTAssertEqual(value, decodedValue, file: file, line: line)
|
|
}
|
|
}
|
|
|
|
private func checkMessageCoding<Notification: NotificationType & Equatable>(
|
|
_ value: Notification,
|
|
json: String,
|
|
file: StaticString = #filePath,
|
|
line: UInt = #line
|
|
) {
|
|
checkCoding(JSONRPCMessage.notification(value), json: json, userInfo: defaultCodingInfo, file: file, line: line) {
|
|
guard case JSONRPCMessage.notification(let decodedValueOpaque) = $0,
|
|
let decodedValue = decodedValueOpaque as? Notification
|
|
else {
|
|
XCTFail("decodedValue \($0) does not match expected \(value)", file: file, line: line)
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(value, decodedValue, file: file, line: line)
|
|
}
|
|
}
|
|
|
|
private func checkMessageCoding<Response: ResponseType & Equatable>(
|
|
_ value: Response,
|
|
id: RequestID,
|
|
json: String,
|
|
file: StaticString = #filePath,
|
|
line: UInt = #line
|
|
) {
|
|
let callback: JSONRPCMessage.ResponseTypeCallback = {
|
|
return $0 == .string("unknown") ? nil : Response.self
|
|
}
|
|
|
|
var codingInfo = defaultCodingInfo
|
|
codingInfo[.responseTypeCallbackKey] = callback
|
|
|
|
checkCoding(JSONRPCMessage.response(value, id: id), json: json, userInfo: codingInfo, file: file, line: line) {
|
|
|
|
guard case JSONRPCMessage.response(let decodedValueOpaque, let decodedID) = $0,
|
|
let decodedValue = decodedValueOpaque as? Response
|
|
else {
|
|
XCTFail("decodedValue \($0) does not match expected \(value)", file: file, line: line)
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(id, decodedID, "requestID decoding", file: file, line: line)
|
|
XCTAssertEqual(value, decodedValue, file: file, line: line)
|
|
}
|
|
}
|
|
|
|
private func checkMessageCoding(
|
|
_ value: ResponseError,
|
|
id: RequestID?,
|
|
json: String,
|
|
file: StaticString = #filePath,
|
|
line: UInt = #line
|
|
) {
|
|
checkCoding(
|
|
JSONRPCMessage.errorResponse(value, id: id),
|
|
json: json,
|
|
userInfo: defaultCodingInfo,
|
|
file: file,
|
|
line: line
|
|
) {
|
|
|
|
guard case JSONRPCMessage.errorResponse(let decodedValue, let decodedID) = $0 else {
|
|
XCTFail("decodedValue \($0) does not match expected \(value)", file: file, line: line)
|
|
return
|
|
}
|
|
|
|
XCTAssertEqual(id, decodedID, "requestID decoding", file: file, line: line)
|
|
XCTAssertEqual(value, decodedValue, file: file, line: line)
|
|
}
|
|
}
|
|
|
|
private func checkMessageDecodingError(
|
|
_ expected: MessageDecodingError,
|
|
json: String,
|
|
userInfo: [CodingUserInfoKey: Any] = defaultCodingInfo,
|
|
file: StaticString = #filePath,
|
|
line: UInt = #line
|
|
) {
|
|
let data = json.data(using: .utf8)!
|
|
let decoder = JSONDecoder()
|
|
decoder.userInfo = userInfo
|
|
|
|
do {
|
|
_ = try decoder.decode(JSONRPCMessage.self, from: data)
|
|
XCTFail("expected error not seen", file: file, line: line)
|
|
} catch let error as MessageDecodingError {
|
|
XCTAssertEqual(expected.code, error.code, file: file, line: line)
|
|
XCTAssertEqual(expected.id, error.id, file: file, line: line)
|
|
XCTAssertTrue(
|
|
error.message.hasPrefix(expected.message),
|
|
"message expected to start with \(expected.message); got \(error.message)",
|
|
file: file,
|
|
line: line
|
|
)
|
|
} catch {
|
|
XCTFail("incorrect error seen \(error)", file: file, line: line)
|
|
}
|
|
}
|