//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2019 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 LanguageServerProtocol import SKTestSupport import XCTest final class CodingTests: XCTestCase { func testValueCoding() throws { let url = URL(fileURLWithPath: "/foo.swift") let uri = DocumentURI(url) let urljson = "file:///foo.swift" let range = Position(line: 5, utf16index: 23).. start, Range.upperBound -> end let rangejson = """ { "end" : { "character" : 0, "line" : 6 }, "start" : { "character" : 23, "line" : 5 } } """ let indent2rangejson = rangejson.indented(2, skipFirstLine: true) // url -> uri checkCoding( Location(uri: uri, range: range), json: """ { "range" : \(indent2rangejson), "uri" : "\(urljson)" } """ ) checkCoding( TextEdit(range: range, newText: "foo"), json: """ { "newText" : "foo", "range" : \(indent2rangejson) } """ ) // url -> uri checkCoding( TextDocumentIdentifier(uri), json: """ { "uri" : "\(urljson)" } """ ) checkCoding( OptionalVersionedTextDocumentIdentifier(uri, version: nil), json: """ { "uri" : "\(urljson)", "version" : null } """ ) checkCoding( VersionedTextDocumentIdentifier(uri, version: 3), json: """ { "uri" : "\(urljson)", "version" : 3 } """ ) checkCoding( TextDocumentEdit( textDocument: OptionalVersionedTextDocumentIdentifier(uri, version: 1), edits: [ .textEdit(TextEdit(range: range, newText: "foo")) ] ), json: """ { "edits" : [ { "newText" : "foo", "range" : \(rangejson.indented(6, skipFirstLine: true)) } ], "textDocument" : { "uri" : "\(urljson)", "version" : 1 } } """ ) // url -> uri checkCoding( WorkspaceFolder(uri: uri, name: "foo"), json: """ { "name" : "foo", "uri" : "\(urljson)" } """ ) checkCoding( WorkspaceFolder(uri: uri), json: """ { "name" : "foo.swift", "uri" : "\(urljson)" } """ ) checkCoding( WorkspaceFolder(uri: uri, name: ""), json: """ { "name" : "unknown_workspace", "uri" : "\(urljson)" } """ ) checkCoding(MarkupKind.markdown, json: "\"markdown\"") checkCoding(MarkupKind.plaintext, json: "\"plaintext\"") checkCoding(SymbolKind.file, json: "1") checkCoding(SymbolKind.class, json: "5") checkCoding(CompletionItemKind.text, json: "1") checkCoding(CompletionItemKind.class, json: "7") checkCoding(CodeActionKind.quickFix, json: "\"quickfix\"") checkCoding(CodeActionKind(rawValue: "x"), json: "\"x\"") checkCoding(ErrorCode.cancelled, json: "-32800") checkCoding(ClientCapabilities(workspace: nil, textDocument: nil), json: "{\n\n}") checkCoding( ClientCapabilities( workspace: with(WorkspaceClientCapabilities()) { $0.applyEdit = true }, textDocument: nil ), json: """ { "workspace" : { "applyEdit" : true } } """ ) checkCoding( WorkspaceSettingsChange.clangd(ClangWorkspaceSettings(compilationDatabasePath: "foo")), json: """ { "compilationDatabasePath" : "foo" } """ ) checkDecoding( json: """ { "hi": "there" } """, expected: WorkspaceSettingsChange.unknown(LSPAny.dictionary(["hi": LSPAny.string("there")])) ) // experimental can be anything checkDecoding( json: """ { "experimenal": [1] } """, expected: ClientCapabilities(workspace: nil, textDocument: nil) ) checkDecoding( json: """ { "workspace": { "workspaceEdit": { "documentChanges": false } } } """, expected: ClientCapabilities( workspace: with(WorkspaceClientCapabilities()) { $0.workspaceEdit = WorkspaceClientCapabilities.WorkspaceEdit(documentChanges: false) }, textDocument: nil ) ) // ignore unknown keys checkDecoding( json: """ { "workspace": { "workspaceEdit": { "ben's unlikley opton": false } } } """, expected: ClientCapabilities( workspace: with(WorkspaceClientCapabilities()) { $0.workspaceEdit = WorkspaceClientCapabilities.WorkspaceEdit(documentChanges: nil) }, textDocument: nil ) ) checkCoding(RequestID.number(100), json: "100") checkCoding(RequestID.string("100"), json: "\"100\"") checkCoding(Language.c, json: "\"c\"") checkCoding(Language.cpp, json: "\"cpp\"") checkCoding(Language.objective_c, json: "\"objective-c\"") checkCoding(Language.objective_cpp, json: "\"objective-cpp\"") checkCoding(Language.swift, json: "\"swift\"") checkCoding(Language(rawValue: "unknown"), json: "\"unknown\"") checkCoding(DiagnosticCode.number(123), json: "123") checkCoding(DiagnosticCode.string("hi"), json: "\"hi\"") checkCoding( CodeDescription(href: try DocumentURI(string: "file:///some/path")), json: """ { "href" : "file:///some/path" } """ ) let markup = MarkupContent(kind: .plaintext, value: "a") checkCoding( HoverResponse(contents: .markupContent(markup), range: nil), json: """ { "contents" : { "kind" : "plaintext", "value" : "a" } } """ ) checkDecoding( json: """ { "contents" : "test" } """, expected: HoverResponse(contents: .markedStrings([.markdown(value: "test")]), range: nil) ) checkCoding( HoverResponse( contents: .markedStrings([.markdown(value: "test"), .codeBlock(language: "swift", value: "let foo = 2")]), range: nil ), json: """ { "contents" : [ "test", { "language" : "swift", "value" : "let foo = 2" } ] } """ ) checkCoding( HoverResponse(contents: .markupContent(markup), range: range), json: """ { "contents" : { "kind" : "plaintext", "value" : "a" }, "range" : \(rangejson.indented(2, skipFirstLine: true)) } """ ) checkCoding( TextDocumentContentChangeEvent(text: "a"), json: """ { "text" : "a" } """ ) checkCoding( TextDocumentContentChangeEvent(range: range, rangeLength: 10, text: "a"), json: """ { "range" : \(rangejson.indented(2, skipFirstLine: true)), "rangeLength" : 10, "text" : "a" } """ ) checkCoding( WorkspaceEdit(changes: [uri: []]), json: """ { "changes" : { "\(urljson)" : [ ] } } """ ) checkCoding( CompletionList(isIncomplete: true, items: [CompletionItem(label: "abc", kind: .function)]), json: """ { "isIncomplete" : true, "items" : [ { "kind" : 3, "label" : "abc" } ] } """ ) checkDecoding( json: """ [ { "kind" : 3, "label" : "abc" } ] """, expected: CompletionList(isIncomplete: false, items: [CompletionItem(label: "abc", kind: .function)]) ) checkCoding( StringOrMarkupContent.markupContent(MarkupContent(kind: .markdown, value: "some **Markdown***")), json: """ { "kind" : "markdown", "value" : "some **Markdown***" } """ ) checkCoding( StringOrMarkupContent.string("Some documentation"), json: """ "Some documentation" """ ) checkCoding(PrepareRenameResponse(range: range), json: rangejson) checkCoding( PrepareRenameResponse(range: range, placeholder: "somePlaceholder"), json: """ { "placeholder" : "somePlaceholder", "range" : \(rangejson.indented(2, skipFirstLine: true)) } """ ) checkCoding( LocationsOrLocationLinksResponse.locations([Location(uri: uri, range: range)]), json: """ [ { "range" : \(rangejson.indented(4, skipFirstLine: true)), "uri" : "\(urljson)" } ] """ ) checkCoding( LocationsOrLocationLinksResponse.locationLinks([ LocationLink(targetUri: uri, targetRange: range, targetSelectionRange: range) ]), json: """ [ { "targetRange" : \(rangejson.indented(4, skipFirstLine: true)), "targetSelectionRange" : \(rangejson.indented(4, skipFirstLine: true)), "targetUri" : "\(urljson)" } ] """ ) checkDecoding( json: """ { "range" : \(rangejson.indented(2, skipFirstLine: true)), "uri" : "\(urljson)" } """, expected: LocationsOrLocationLinksResponse.locations([Location(uri: uri, range: range)]) ) checkCoding( DocumentSymbolResponse.documentSymbols([ DocumentSymbol(name: "mySymbol", kind: .function, range: range, selectionRange: range) ]), json: """ [ { "kind" : 12, "name" : "mySymbol", "range" : \(rangejson.indented(4, skipFirstLine: true)), "selectionRange" : \(rangejson.indented(4, skipFirstLine: true)) } ] """ ) checkCoding( DocumentSymbolResponse.symbolInformation([ SymbolInformation(name: "mySymbol", kind: .function, location: Location(uri: uri, range: range)) ]), json: """ [ { "kind" : 12, "location" : { "range" : \(rangejson.indented(6, skipFirstLine: true)), "uri" : "\(urljson)" }, "name" : "mySymbol" } ] """ ) checkCoding(ValueOrBool.value(5), json: "5") checkCoding(ValueOrBool.bool(false), json: "false") checkDecoding( json: "2", expected: TextDocumentSyncOptions( openClose: nil, change: .incremental, willSave: nil, willSaveWaitUntil: nil, save: nil ) ) checkCoding( TextDocumentSyncOptions(), json: """ { "change" : 2, "openClose" : true, "save" : { "includeText" : false }, "willSave" : true, "willSaveWaitUntil" : false } """ ) checkCoding( WorkspaceEdit(documentChanges: [ .textDocumentEdit( TextDocumentEdit(textDocument: OptionalVersionedTextDocumentIdentifier(uri, version: 2), edits: []) ) ]), json: """ { "documentChanges" : [ { "edits" : [ ], "textDocument" : { "uri" : "\(urljson)", "version" : 2 } } ] } """ ) checkCoding( WorkspaceEdit(documentChanges: [.createFile(CreateFile(uri: uri))]), json: """ { "documentChanges" : [ { "kind" : "create", "uri" : "\(urljson)" } ] } """ ) checkCoding( WorkspaceEdit(documentChanges: [.renameFile(RenameFile(oldUri: uri, newUri: uri))]), json: """ { "documentChanges" : [ { "kind" : "rename", "newUri" : "\(urljson)", "oldUri" : "\(urljson)" } ] } """ ) checkCoding( WorkspaceEdit(documentChanges: [.deleteFile(DeleteFile(uri: uri))]), json: """ { "documentChanges" : [ { "kind" : "delete", "uri" : "\(urljson)" } ] } """ ) } func testValueOrBool() { XCTAssertTrue(ValueOrBool.value(5).isSupported) XCTAssertTrue(ValueOrBool.value(0).isSupported) XCTAssertTrue(ValueOrBool.bool(true).isSupported) XCTAssertFalse(ValueOrBool.bool(false).isSupported) } func testPositionRange() { struct WithPosRange: Codable, Equatable { @CustomCodable var range: Range } let range = Position(line: 5, utf16index: 23).. var ranges: [Range] } let ranges = [ Position(line: 1, utf16index: 0).. var range: Range? } let range = Position(line: 5, utf16index: 23)..(_ value: T, mutate: (inout T) -> Void) -> T { var localCopy = value mutate(&localCopy) return localCopy } extension String { func indented(_ spaces: Int, skipFirstLine: Bool = false) -> String { let spaces = String(repeating: " ", count: spaces) return (skipFirstLine ? "" : spaces) + self.replacingOccurrences(of: "\n", with: "\n" + spaces) } }