mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-06 18:24:36 +01:00
If a document is re-opened really quickly after a previous open or modification, we might get a notification from the previous state of the document when its AST finishes building. In normal operation, this is benign, because when we make a request to get the new diagnostics and semantic highlights, they are guaranteed to be for the new document. But in testing, we don't generally anticipate these spurious notifications. The ideal fix for this would be to shutdown and restart sourcekitd server between tests. Currently there are bugs preventing that from working in sourcekitd, so as a workaround, make sure all URLs in tests are uniqued so they will not collide. rdar://59488048
1361 lines
50 KiB
Swift
1361 lines
50 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 LanguageServerProtocol
|
|
import LSPLogging
|
|
import LSPTestSupport
|
|
import SKTestSupport
|
|
import SourceKit
|
|
import XCTest
|
|
|
|
// Workaround ambiguity with Foundation.
|
|
typealias Notification = LanguageServerProtocol.Notification
|
|
|
|
final class LocalSwiftTests: XCTestCase {
|
|
|
|
/// Connection and lifetime management for the service.
|
|
var connection: TestSourceKitServer! = nil
|
|
|
|
/// The primary interface to make requests to the SourceKitServer.
|
|
var sk: TestClient! = nil
|
|
|
|
/// The server's workspace data. Accessing this is unsafe if the server does so concurrently.
|
|
var workspace: Workspace! {
|
|
connection.server!.workspace!
|
|
}
|
|
|
|
override func setUp() {
|
|
connection = TestSourceKitServer()
|
|
sk = connection.client
|
|
_ = try! sk.sendSync(InitializeRequest(
|
|
processId: nil,
|
|
rootPath: nil,
|
|
rootURI: nil,
|
|
initializationOptions: nil,
|
|
capabilities: ClientCapabilities(workspace: nil,
|
|
textDocument: TextDocumentClientCapabilities(
|
|
codeAction: .init(
|
|
codeActionLiteralSupport: .init(
|
|
codeActionKind: .init(valueSet: [.quickFix])
|
|
)))),
|
|
trace: .off,
|
|
workspaceFolders: nil))
|
|
}
|
|
|
|
override func tearDown() {
|
|
sk = nil
|
|
connection = nil
|
|
}
|
|
|
|
func testEditing() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
sk.allowUnexpectedNotification = false
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri,
|
|
language: .swift,
|
|
version: 12,
|
|
text: """
|
|
func
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("func", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 0, utf16index: 4))
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 13), contentChanges: [
|
|
.init(range: Range(Position(line: 0, utf16index: 4)), text: " foo() {}\n")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 1 - syntactic")
|
|
// 1 = remaining semantic error
|
|
// 0 = semantic update finished already
|
|
XCTAssertEqual(note.params.version, 13)
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("func foo() {}\n", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 1 - semantic")
|
|
XCTAssertEqual(note.params.version, 13)
|
|
XCTAssertEqual(note.params.diagnostics.count, 0)
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 14), contentChanges: [
|
|
.init(range: Range(Position(line: 1, utf16index: 0)), text: "bar()")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 2 - syntactic")
|
|
XCTAssertEqual(note.params.version, 14)
|
|
// 1 = semantic update finished already
|
|
// 0 = only syntactic
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("""
|
|
func foo() {}
|
|
bar()
|
|
""", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 2 - semantic")
|
|
XCTAssertEqual(note.params.version, 14)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 1, utf16index: 0))
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 14), contentChanges: [
|
|
.init(range: Position(line: 1, utf16index: 0)..<Position(line: 1, utf16index: 3), text: "foo")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 3 - syntactic")
|
|
// 1 = remaining semantic error
|
|
// 0 = semantic update finished already
|
|
XCTAssertEqual(note.params.version, 14)
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("""
|
|
func foo() {}
|
|
foo()
|
|
""", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 3 - semantic")
|
|
XCTAssertEqual(note.params.version, 14)
|
|
XCTAssertEqual(note.params.diagnostics.count, 0)
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 15), contentChanges: [
|
|
.init(range: Position(line: 1, utf16index: 0)..<Position(line: 1, utf16index: 3), text: "fooTypo")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 4 - syntactic")
|
|
XCTAssertEqual(note.params.version, 15)
|
|
// 1 = semantic update finished already
|
|
// 0 = only syntactic
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("""
|
|
func foo() {}
|
|
fooTypo()
|
|
""", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 4 - semantic")
|
|
XCTAssertEqual(note.params.version, 15)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 1, utf16index: 0))
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 16), contentChanges: [
|
|
.init(range: nil, text: """
|
|
func bar() {}
|
|
foo()
|
|
""")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 5 - syntactic")
|
|
XCTAssertEqual(note.params.version, 16)
|
|
// Could be remaining semantic error or new one.
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("""
|
|
func bar() {}
|
|
foo()
|
|
""", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 5 - semantic")
|
|
XCTAssertEqual(note.params.version, 16)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 1, utf16index: 0))
|
|
})
|
|
}
|
|
|
|
func testEditingNonURL() {
|
|
let uri = DocumentURI(string: "urn:uuid:A1B08909-E791-469E-BF0F-F5790977E051")
|
|
|
|
sk.allowUnexpectedNotification = false
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri,
|
|
language: .swift,
|
|
version: 12,
|
|
text: """
|
|
func
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("func", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 0, utf16index: 4))
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 13), contentChanges: [
|
|
.init(range: Range(Position(line: 0, utf16index: 4)), text: " foo() {}\n")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 1 - syntactic")
|
|
XCTAssertEqual(note.params.version, 13)
|
|
// 1 = remaining semantic error
|
|
// 0 = semantic update finished already
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("func foo() {}\n", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 1 - semantic")
|
|
XCTAssertEqual(note.params.version, 13)
|
|
XCTAssertEqual(note.params.diagnostics.count, 0)
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 14), contentChanges: [
|
|
.init(range: Range(Position(line: 1, utf16index: 0)), text: "bar()")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 2 - syntactic")
|
|
XCTAssertEqual(note.params.version, 14)
|
|
// 1 = semantic update finished already
|
|
// 0 = only syntactic
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("""
|
|
func foo() {}
|
|
bar()
|
|
""", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 2 - semantic")
|
|
XCTAssertEqual(note.params.version, 14)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 1, utf16index: 0))
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 14), contentChanges: [
|
|
.init(range: Position(line: 1, utf16index: 0)..<Position(line: 1, utf16index: 3), text: "foo")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 3 - syntactic")
|
|
XCTAssertEqual(note.params.version, 14)
|
|
// 1 = remaining semantic error
|
|
// 0 = semantic update finished already
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("""
|
|
func foo() {}
|
|
foo()
|
|
""", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 3 - semantic")
|
|
XCTAssertEqual(note.params.version, 14)
|
|
XCTAssertEqual(note.params.diagnostics.count, 0)
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 15), contentChanges: [
|
|
.init(range: Position(line: 1, utf16index: 0)..<Position(line: 1, utf16index: 3), text: "fooTypo")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 4 - syntactic")
|
|
XCTAssertEqual(note.params.version, 15)
|
|
// 1 = semantic update finished already
|
|
// 0 = only syntactic
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("""
|
|
func foo() {}
|
|
fooTypo()
|
|
""", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 4 - semantic")
|
|
XCTAssertEqual(note.params.version, 15)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 1, utf16index: 0))
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uri, version: 16), contentChanges: [
|
|
.init(range: nil, text: """
|
|
func bar() {}
|
|
foo()
|
|
""")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 5 - syntactic")
|
|
XCTAssertEqual(note.params.version, 16)
|
|
// Could be remaining semantic error or new one.
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual("""
|
|
func bar() {}
|
|
foo()
|
|
""", self.workspace.documentManager.latestSnapshot(uri)!.text)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 5 - semantic")
|
|
XCTAssertEqual(note.params.version, 16)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 1, utf16index: 0))
|
|
})
|
|
}
|
|
|
|
func testExcludedDocumentSchemeDiagnostics() {
|
|
let includedURL = URL(fileURLWithPath: "/a.swift")
|
|
let includedURI = DocumentURI(includedURL)
|
|
|
|
let excludedURI = DocumentURI(string: "git:/a.swift")
|
|
|
|
let text = """
|
|
func
|
|
"""
|
|
|
|
sk.allowUnexpectedNotification = false
|
|
|
|
// Open the excluded URI first so our later notification handlers can confirm
|
|
// that no diagnostics were emitted for this excluded URI.
|
|
sk.send(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: excludedURI,
|
|
language: .swift,
|
|
version: 1,
|
|
text: text
|
|
)))
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: includedURI,
|
|
language: .swift,
|
|
version: 1,
|
|
text: text
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
XCTAssertEqual(note.params.uri, includedURI)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.uri, includedURI)
|
|
})
|
|
}
|
|
|
|
func testCrossFileDiagnostics() {
|
|
let urlA = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let urlB = URL(fileURLWithPath: "/\(#function)/b.swift")
|
|
let uriA = DocumentURI(urlA)
|
|
let uriB = DocumentURI(urlB)
|
|
|
|
sk.allowUnexpectedNotification = false
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uriA, language: .swift, version: 12,
|
|
text: """
|
|
foo()
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
// 1 = semantic update finished already
|
|
// 0 = only syntactic
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 0, utf16index: 0))
|
|
})
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uriB, language: .swift, version: 12,
|
|
text: """
|
|
bar()
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
// 1 = semantic update finished already
|
|
// 0 = only syntactic
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 0, utf16index: 0))
|
|
})
|
|
|
|
sk.sendNoteSync(DidChangeTextDocumentNotification(textDocument: .init(uriA, version: 13), contentChanges: [
|
|
.init(range: nil, text: "foo()\n")
|
|
]), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 1 - syntactic")
|
|
XCTAssertEqual(note.params.version, 13)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for edit 1 - semantic")
|
|
XCTAssertEqual(note.params.version, 13)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
})
|
|
}
|
|
|
|
func testDiagnosticsReopen() {
|
|
let urlA = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uriA = DocumentURI(urlA)
|
|
sk.allowUnexpectedNotification = false
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uriA, language: .swift, version: 12,
|
|
text: """
|
|
foo()
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
// 1 = semantic update finished already
|
|
// 0 = only syntactic
|
|
XCTAssertLessThanOrEqual(note.params.diagnostics.count, 1)
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.version, 12)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 0, utf16index: 0))
|
|
})
|
|
|
|
sk.send(DidCloseTextDocumentNotification(textDocument: .init(urlA)))
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uriA, language: .swift, version: 13,
|
|
text: """
|
|
var
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
XCTAssertEqual(note.params.version, 13)
|
|
// 1 = syntactic, no cached semantic diagnostic from previous version
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 0, utf16index: 3))
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.version, 13)
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
XCTAssertEqual(
|
|
note.params.diagnostics.first?.range.lowerBound,
|
|
Position(line: 0, utf16index: 3))
|
|
})
|
|
}
|
|
|
|
func testFixitsAreIncludedInPublishDiagnostics() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
sk.allowUnexpectedNotification = false
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri, language: .swift, version: 12,
|
|
text: """
|
|
func foo() {
|
|
let a = 2
|
|
}
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
let diag = note.params.diagnostics.first!
|
|
XCTAssertNotNil(diag.codeActions)
|
|
XCTAssertEqual(diag.codeActions!.count, 1)
|
|
let fixit = diag.codeActions!.first!
|
|
|
|
// Expected Fix-it: Replace `let a` with `_` because it's never used
|
|
let expectedTextEdit = TextEdit(range: Position(line: 1, utf16index: 2)..<Position(line: 1, utf16index: 7), newText: "_")
|
|
XCTAssertEqual(fixit, CodeAction(
|
|
title: "Replace 'let a' with '_'",
|
|
kind: .quickFix,
|
|
diagnostics: nil,
|
|
edit: WorkspaceEdit(changes: [uri: [expectedTextEdit]], documentChanges: nil),
|
|
command: nil))
|
|
})
|
|
}
|
|
|
|
func testFixitsAreIncludedInPublishDiagnosticsNotes() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
sk.allowUnexpectedNotification = false
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri, language: .swift, version: 12,
|
|
text: """
|
|
func foo(a: Int?) {
|
|
_ = a.bigEndian
|
|
}
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
let diag = note.params.diagnostics.first!
|
|
XCTAssertEqual(diag.relatedInformation?.count, 2)
|
|
if let note1 = diag.relatedInformation?.first(where: { $0.message.contains("'?'") }) {
|
|
XCTAssertEqual(note1.codeActions?.count, 1)
|
|
if let fixit = note1.codeActions?.first {
|
|
// Expected Fix-it: Replace `let a` with `_` because it's never used
|
|
let expectedTextEdit = TextEdit(range: Position(line: 1, utf16index: 7)..<Position(line: 1, utf16index: 7), newText: "?")
|
|
XCTAssertEqual(fixit, CodeAction(
|
|
title: "chain the optional using '?' to access member 'bigEndian' only for non-'nil' base values",
|
|
kind: .quickFix,
|
|
diagnostics: nil,
|
|
edit: WorkspaceEdit(changes: [uri: [expectedTextEdit]], documentChanges: nil),
|
|
command: nil))
|
|
}
|
|
} else {
|
|
XCTFail("missing '?' note")
|
|
}
|
|
if let note2 = diag.relatedInformation?.first(where: { $0.message.contains("'!'") }) {
|
|
XCTAssertEqual(note2.codeActions?.count, 1)
|
|
if let fixit = note2.codeActions?.first {
|
|
// Expected Fix-it: Replace `let a` with `_` because it's never used
|
|
let expectedTextEdit = TextEdit(range: Position(line: 1, utf16index: 7)..<Position(line: 1, utf16index: 7), newText: "!")
|
|
XCTAssertEqual(fixit, CodeAction(
|
|
title: "force-unwrap using '!' to abort execution if the optional value contains 'nil'",
|
|
kind: .quickFix,
|
|
diagnostics: nil,
|
|
edit: WorkspaceEdit(changes: [uri: [expectedTextEdit]], documentChanges: nil),
|
|
command: nil))
|
|
}
|
|
} else {
|
|
XCTFail("missing '!' note")
|
|
}
|
|
})
|
|
}
|
|
|
|
func testFixitInsert() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
sk.allowUnexpectedNotification = false
|
|
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri, language: .swift, version: 12,
|
|
text: """
|
|
func foo() {
|
|
print("")print("")
|
|
}
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
let diag = note.params.diagnostics.first!
|
|
XCTAssertNotNil(diag.codeActions)
|
|
XCTAssertEqual(diag.codeActions!.count, 1)
|
|
let fixit = diag.codeActions!.first!
|
|
|
|
// Expected Fix-it: Insert `;`
|
|
let expectedTextEdit = TextEdit(range: Position(line: 1, utf16index: 11)..<Position(line: 1, utf16index: 11), newText: ";")
|
|
XCTAssertEqual(fixit, CodeAction(
|
|
title: "Insert ';'",
|
|
kind: .quickFix,
|
|
diagnostics: nil,
|
|
edit: WorkspaceEdit(changes: [uri: [expectedTextEdit]], documentChanges: nil),
|
|
command: nil))
|
|
})
|
|
}
|
|
|
|
func testFixitTitle() {
|
|
XCTAssertEqual("Insert ';'", CodeAction.fixitTitle(replace: "", with: ";"))
|
|
XCTAssertEqual("Replace 'let a' with '_'", CodeAction.fixitTitle(replace: "let a", with: "_"))
|
|
XCTAssertEqual("Remove 'foo ='", CodeAction.fixitTitle(replace: "foo =", with: ""))
|
|
}
|
|
|
|
func testFixitsAreReturnedFromCodeActions() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
var diagnostic: Diagnostic! = nil
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri, language: .swift, version: 12,
|
|
text: """
|
|
func foo() {
|
|
let a = 2
|
|
}
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
diagnostic = note.params.diagnostics.first!
|
|
})
|
|
|
|
let request = CodeActionRequest(
|
|
range: Position(line: 1, utf16index: 0)..<Position(line: 1, utf16index: 11),
|
|
context: CodeActionContext(diagnostics: [diagnostic], only: nil),
|
|
textDocument: TextDocumentIdentifier(uri)
|
|
)
|
|
let response = try! sk.sendSync(request)
|
|
|
|
XCTAssertNotNil(response)
|
|
guard case .codeActions(let codeActions) = response else {
|
|
XCTFail("Expected code actions as response")
|
|
return
|
|
}
|
|
let quickFixes = codeActions.filter{ $0.kind == .quickFix }
|
|
XCTAssertEqual(quickFixes.count, 1)
|
|
let fixit = quickFixes.first!
|
|
|
|
// Expected Fix-it: Replace `let a` with `_` because it's never used
|
|
let expectedTextEdit = TextEdit(range: Position(line: 1, utf16index: 2)..<Position(line: 1, utf16index: 7), newText: "_")
|
|
XCTAssertEqual(fixit, CodeAction(
|
|
title: "Replace 'let a' with '_'",
|
|
kind: .quickFix,
|
|
diagnostics: [
|
|
Diagnostic(
|
|
range: Position(line: 1, utf16index: 6)..<Position(line: 1, utf16index: 6),
|
|
severity: .warning,
|
|
code: nil,
|
|
source: "sourcekitd",
|
|
message: "initialization of immutable value \'a\' was never used; consider replacing with assignment to \'_\' or removing it",
|
|
relatedInformation: [],
|
|
codeActions: nil)],
|
|
edit: WorkspaceEdit(changes: [uri: [expectedTextEdit]], documentChanges: nil),
|
|
command: nil))
|
|
}
|
|
|
|
func testFixitsAreReturnedFromCodeActionsNotes() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
var diagnostic: Diagnostic! = nil
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri, language: .swift, version: 12,
|
|
text: """
|
|
func foo(a: Int?) {
|
|
_ = a.bigEndian
|
|
}
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
diagnostic = note.params.diagnostics.first!
|
|
})
|
|
|
|
let request = CodeActionRequest(
|
|
range: Position(line: 1, utf16index: 0)..<Position(line: 1, utf16index: 11),
|
|
context: CodeActionContext(diagnostics: [diagnostic], only: nil),
|
|
textDocument: TextDocumentIdentifier(uri)
|
|
)
|
|
let response = try! sk.sendSync(request)
|
|
|
|
XCTAssertNotNil(response)
|
|
guard case .codeActions(let codeActions) = response else {
|
|
XCTFail("Expected code actions as response")
|
|
return
|
|
}
|
|
let quickFixes = codeActions.filter{ $0.kind == .quickFix }
|
|
XCTAssertEqual(quickFixes.count, 2)
|
|
|
|
var expectedTextEdit = TextEdit(range: Position(line: 1, utf16index: 7)..<Position(line: 1, utf16index: 7), newText: "_")
|
|
|
|
for fixit in quickFixes {
|
|
if fixit.title.contains("!") {
|
|
XCTAssert(fixit.title.starts(with: "force-unwrap using '!'"))
|
|
expectedTextEdit.newText = "!"
|
|
XCTAssertEqual(fixit.edit, WorkspaceEdit(changes: [uri: [expectedTextEdit]], documentChanges: nil))
|
|
} else {
|
|
XCTAssert(fixit.title.starts(with: "chain the optional using '?'"))
|
|
expectedTextEdit.newText = "?"
|
|
XCTAssertEqual(fixit.edit, WorkspaceEdit(changes: [uri: [expectedTextEdit]], documentChanges: nil))
|
|
}
|
|
XCTAssertEqual(fixit.kind, .quickFix)
|
|
XCTAssertEqual(fixit.diagnostics?.count, 1)
|
|
XCTAssertEqual(fixit.diagnostics?.first?.severity, .error)
|
|
XCTAssertEqual(fixit.diagnostics?.first?.range, Range(Position(line: 1, utf16index: 6)))
|
|
XCTAssert(fixit.diagnostics?.first?.message.starts(with: "value of optional type") == true)
|
|
}
|
|
}
|
|
|
|
func testMuliEditFixitCodeActionPrimary() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
var diagnostic: Diagnostic! = nil
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri, language: .swift, version: 12,
|
|
text: """
|
|
@available(*, introduced: 10, deprecated: 11)
|
|
func foo() {}
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
diagnostic = note.params.diagnostics.first!
|
|
})
|
|
|
|
let request = CodeActionRequest(
|
|
range: Position(line: 0, utf16index: 1)..<Position(line: 0, utf16index: 10),
|
|
context: CodeActionContext(diagnostics: [diagnostic], only: nil),
|
|
textDocument: TextDocumentIdentifier(uri)
|
|
)
|
|
let response = try! sk.sendSync(request)
|
|
|
|
XCTAssertNotNil(response)
|
|
guard case .codeActions(let codeActions) = response else {
|
|
XCTFail("Expected code actions as response")
|
|
return
|
|
}
|
|
let quickFixes = codeActions.filter{ $0.kind == .quickFix }
|
|
XCTAssertEqual(quickFixes.count, 1)
|
|
guard let fixit = quickFixes.first else { return }
|
|
|
|
XCTAssertEqual(fixit.title, "Remove ': 10'...")
|
|
XCTAssertEqual(fixit.diagnostics?.count, 1)
|
|
XCTAssertEqual(fixit.edit?.changes?[uri], [
|
|
TextEdit(range: Position(line: 0, utf16index: 24)..<Position(line: 0, utf16index: 28), newText: ""),
|
|
TextEdit(range: Position(line: 0, utf16index: 40)..<Position(line: 0, utf16index: 44), newText: ""),
|
|
])
|
|
}
|
|
|
|
func testMuliEditFixitCodeActionNote() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
var diagnostic: Diagnostic! = nil
|
|
sk.sendNoteSync(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri, language: .swift, version: 12,
|
|
text: """
|
|
@available(*, deprecated, renamed: "new(_:hotness:)")
|
|
func old(and: Int, busted: Int) {}
|
|
func test() {
|
|
old(and: 1, busted: 2)
|
|
}
|
|
"""
|
|
)), { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - syntactic")
|
|
}, { (note: Notification<PublishDiagnosticsNotification>) in
|
|
log("Received diagnostics for open - semantic")
|
|
XCTAssertEqual(note.params.diagnostics.count, 1)
|
|
diagnostic = note.params.diagnostics.first!
|
|
})
|
|
|
|
let request = CodeActionRequest(
|
|
range: Position(line: 3, utf16index: 2)..<Position(line: 3, utf16index: 2),
|
|
context: CodeActionContext(diagnostics: [diagnostic], only: nil),
|
|
textDocument: TextDocumentIdentifier(uri)
|
|
)
|
|
let response = try! sk.sendSync(request)
|
|
|
|
XCTAssertNotNil(response)
|
|
guard case .codeActions(let codeActions) = response else {
|
|
XCTFail("Expected code actions as response")
|
|
return
|
|
}
|
|
let quickFixes = codeActions.filter{ $0.kind == .quickFix }
|
|
XCTAssertEqual(quickFixes.count, 1)
|
|
guard let fixit = quickFixes.first else { return }
|
|
|
|
XCTAssertEqual(fixit.title, "use 'new(_:hotness:)' instead")
|
|
XCTAssertEqual(fixit.diagnostics?.count, 1)
|
|
XCTAssert(fixit.diagnostics?.first?.message.contains("is deprecated") == true)
|
|
XCTAssertEqual(fixit.edit?.changes?[uri], [
|
|
TextEdit(range: Position(line: 3, utf16index: 2)..<Position(line: 3, utf16index: 5), newText: "new"),
|
|
TextEdit(range: Position(line: 3, utf16index: 6)..<Position(line: 3, utf16index: 11), newText: ""),
|
|
TextEdit(range: Position(line: 3, utf16index: 14)..<Position(line: 3, utf16index: 20), newText: "hotness"),
|
|
])
|
|
}
|
|
|
|
func testXMLToMarkdownDeclaration() {
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Declaration>func foo(_ bar: <Type usr="fake">Baz</Type>)</Declaration>
|
|
"""), """
|
|
```
|
|
func foo(_ bar: Baz)
|
|
```
|
|
""")
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Declaration>func foo() -> <Type>Bar</Type></Declaration>
|
|
"""), """
|
|
```
|
|
func foo() -> Bar
|
|
```
|
|
""")
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Declaration>func replacingOccurrences<Target, Replacement>(of target: Target, with replacement: Replacement, options: <Type usr="s:SS">String</Type>.<Type usr="s:SS10FoundationE14CompareOptionsa">CompareOptions</Type> = default, range searchRange: <Type usr="s:Sn">Range</Type><<Type usr="s:SS">String</Type>.<Type usr="s:SS5IndexV">Index</Type>>? = default) -> <Type usr="s:SS">String</Type> where Target : <Type usr="s:Sy">StringProtocol</Type>, Replacement : <Type usr="s:Sy">StringProtocol</Type></Declaration>
|
|
"""), """
|
|
```
|
|
func replacingOccurrences<Target, Replacement>(of target: Target, with replacement: Replacement, options: String.CompareOptions = default, range searchRange: Range<String.Index>? = default) -> String where Target : StringProtocol, Replacement : StringProtocol
|
|
```
|
|
""")
|
|
}
|
|
|
|
func testXMLToMarkdownComment() {
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Class><Declaration>var foo</Declaration></Class>
|
|
"""), """
|
|
```
|
|
var foo
|
|
```
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Class><Name>foo</Name><Declaration>var foo</Declaration></Class>
|
|
"""), """
|
|
```
|
|
var foo
|
|
```
|
|
""")
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Class><USR>asdf</USR><Declaration>var foo</Declaration><Name>foo</Name></Class>
|
|
"""), """
|
|
```
|
|
var foo
|
|
```
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Class><Abstract>FOO</Abstract></Class>
|
|
"""), """
|
|
FOO
|
|
""")
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Class><Abstract>FOO</Abstract><Declaration>var foo</Declaration></Class>
|
|
"""), """
|
|
FOO
|
|
|
|
```
|
|
var foo
|
|
```
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Class><Abstract>FOO</Abstract><Discussion>BAR</Discussion></Class>
|
|
"""), """
|
|
FOO
|
|
|
|
### Discussion
|
|
|
|
BAR
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Class><Para>A</Para><Para>B</Para><Para>C</Para></Class>
|
|
"""), """
|
|
A
|
|
|
|
B
|
|
|
|
C
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<CodeListing>a</CodeListing>
|
|
"""), """
|
|
```
|
|
a
|
|
```
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<CodeListing><zCodeLineNumbered>a</zCodeLineNumbered></CodeListing>
|
|
"""), """
|
|
```
|
|
1.\ta
|
|
```
|
|
""")
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<CodeListing><zCodeLineNumbered>a</zCodeLineNumbered><zCodeLineNumbered>b</zCodeLineNumbered></CodeListing>
|
|
"""), """
|
|
```
|
|
1.\ta
|
|
2.\tb
|
|
```
|
|
""")
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Class><CodeListing><zCodeLineNumbered>a</zCodeLineNumbered><zCodeLineNumbered>b</zCodeLineNumbered></CodeListing><CodeListing><zCodeLineNumbered>c</zCodeLineNumbered><zCodeLineNumbered>d</zCodeLineNumbered></CodeListing></Class>
|
|
"""), """
|
|
```
|
|
1.\ta
|
|
2.\tb
|
|
```
|
|
|
|
```
|
|
1.\tc
|
|
2.\td
|
|
```
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Para>a b c <codeVoice>d e f</codeVoice> g h i</Para>
|
|
"""), """
|
|
a b c `d e f` g h i
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Para>a b c <emphasis>d e f</emphasis> g h i</Para>
|
|
"""), """
|
|
a b c *d e f* g h i
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Para>a b c <bold>d e f</bold> g h i</Para>
|
|
"""), """
|
|
a b c **d e f** g h i
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Para>a b c<h1>d e f</h1>g h i</Para>
|
|
"""), """
|
|
a b c
|
|
|
|
# d e f
|
|
|
|
g h i
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<Para>a b c<h3>d e f</h3>g h i</Para>
|
|
"""), """
|
|
a b c
|
|
|
|
### d e f
|
|
|
|
g h i
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown(
|
|
"<Class>" +
|
|
"<Name>String</Name>" +
|
|
"<USR>s:SS</USR>" +
|
|
"<Declaration>struct String</Declaration>" +
|
|
"<CommentParts>" +
|
|
"<Abstract>" +
|
|
"<Para>A Unicode s</Para>" +
|
|
"</Abstract>" +
|
|
"<Discussion>" +
|
|
"<Para>A string is a series of characters, such as <codeVoice>"Swift"</codeVoice>, that forms a collection. " +
|
|
"The <codeVoice>String</codeVoice> type bridges with the Objective-C class <codeVoice>NSString</codeVoice> and offers" +
|
|
"</Para>" +
|
|
"<Para>You can create new strings A <emphasis>string literal</emphasis> i" +
|
|
"</Para>" +
|
|
"<CodeListing language=\"swift\">" +
|
|
"<zCodeLineNumbered><![CDATA[let greeting = \"Welcome!\"]]></zCodeLineNumbered>" +
|
|
"<zCodeLineNumbered></zCodeLineNumbered>" +
|
|
"</CodeListing>" +
|
|
"<Para>...</Para>" +
|
|
"<CodeListing language=\"swift\">" +
|
|
"<zCodeLineNumbered><![CDATA[let greeting = \"Welcome!\"]]></zCodeLineNumbered>" +
|
|
"<zCodeLineNumbered></zCodeLineNumbered>" +
|
|
"</CodeListing>" +
|
|
"</Discussion>" +
|
|
"</CommentParts>" +
|
|
"</Class>"
|
|
), """
|
|
```
|
|
struct String
|
|
```
|
|
|
|
A Unicode s
|
|
|
|
### Discussion
|
|
|
|
A string is a series of characters, such as `"Swift"`, that forms a collection. The `String` type bridges with the Objective-C class `NSString` and offers
|
|
|
|
You can create new strings A *string literal* i
|
|
|
|
```
|
|
1.\tlet greeting = "Welcome!"
|
|
2.\t
|
|
```
|
|
|
|
...
|
|
|
|
```
|
|
1.\tlet greeting = "Welcome!"
|
|
2.\t
|
|
```
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown(
|
|
"<Function file=\"DocumentManager.swift\" line=\"92\" column=\"15\">" +
|
|
"<CommentParts>" +
|
|
"<Abstract><Para>Applies the given edits to the document.</Para></Abstract>" +
|
|
"<Parameters>" +
|
|
"<Parameter>" +
|
|
"<Name>editCallback</Name>" +
|
|
"<Direction isExplicit=\"0\">in</Direction>" +
|
|
"<Discussion><Para>Optional closure to call for each edit.</Para></Discussion>" +
|
|
"</Parameter>" +
|
|
"<Parameter>" +
|
|
"<Name>before</Name>" +
|
|
"<Direction isExplicit=\"0\">in</Direction>" +
|
|
"<Discussion><Para>The document contents <emphasis>before</emphasis> the edit is applied.</Para></Discussion>" +
|
|
"</Parameter>" +
|
|
"</Parameters>" +
|
|
"</CommentParts>" +
|
|
"</Function>"), """
|
|
Applies the given edits to the document.
|
|
|
|
- Parameters:
|
|
- editCallback: Optional closure to call for each edit.
|
|
- before: The document contents *before* the edit is applied.
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<ResultDiscussion><Para>The contents of the file after all the edits are applied.</Para></ResultDiscussion>
|
|
"""), """
|
|
### Returns
|
|
|
|
The contents of the file after all the edits are applied.
|
|
""")
|
|
|
|
XCTAssertEqual(try! xmlDocumentationToMarkdown("""
|
|
<ThrowsDiscussion><Para>Error.missingDocument if the document is not open.</Para></ThrowsDiscussion>
|
|
"""), """
|
|
### Throws
|
|
|
|
Error.missingDocument if the document is not open.
|
|
""")
|
|
}
|
|
|
|
func testSymbolInfo() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
sk.send(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri,
|
|
language: .swift,
|
|
version: 1,
|
|
text: """
|
|
struct S {
|
|
func foo() {
|
|
var local = 1
|
|
}
|
|
}
|
|
""")))
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(SymbolInfoRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 0, utf16index: 7)))
|
|
|
|
XCTAssertEqual(resp.count, 1)
|
|
if let sym = resp.first {
|
|
XCTAssertEqual(sym.name, "S")
|
|
XCTAssertNil(sym.containerName)
|
|
XCTAssertEqual(sym.usr, "s:1a1SV")
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.uri, uri)
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.range.lowerBound.line, 0)
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.range.lowerBound.utf16index, 7)
|
|
}
|
|
}
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(SymbolInfoRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 1, utf16index: 7)))
|
|
|
|
XCTAssertEqual(resp.count, 1)
|
|
if let sym = resp.first {
|
|
XCTAssertEqual(sym.name, "foo()")
|
|
XCTAssertNil(sym.containerName)
|
|
XCTAssertEqual(sym.usr, "s:1a1SV3fooyyF")
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.uri, uri)
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.range.lowerBound.line, 1)
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.range.lowerBound.utf16index, 7)
|
|
}
|
|
}
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(SymbolInfoRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 2, utf16index: 8)))
|
|
|
|
XCTAssertEqual(resp.count, 1)
|
|
if let sym = resp.first {
|
|
XCTAssertEqual(sym.name, "local")
|
|
XCTAssertNil(sym.containerName)
|
|
XCTAssertEqual(sym.usr, "s:1a1SV3fooyyF5localL_Sivp")
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.uri, uri)
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.range.lowerBound.line, 2)
|
|
XCTAssertEqual(sym.bestLocalDeclaration?.range.lowerBound.utf16index, 8)
|
|
}
|
|
}
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(SymbolInfoRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 3, utf16index: 0)))
|
|
|
|
XCTAssertEqual(resp.count, 0)
|
|
}
|
|
}
|
|
|
|
func testHover() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
sk.send(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri,
|
|
language: .swift,
|
|
version: 1,
|
|
text: """
|
|
/// This is a doc comment for S.
|
|
///
|
|
/// Details.
|
|
struct S {}
|
|
""")))
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(HoverRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 3, utf16index: 7)))
|
|
|
|
XCTAssertNotNil(resp)
|
|
if let hover = resp {
|
|
XCTAssertNil(hover.range)
|
|
guard case .markupContent(let content) = hover.contents else {
|
|
XCTFail("hover.contents is not .markupContents")
|
|
return
|
|
}
|
|
XCTAssertEqual(content.kind, .markdown)
|
|
XCTAssertEqual(content.value, """
|
|
# S
|
|
```
|
|
struct S
|
|
```
|
|
|
|
This is a doc comment for S.
|
|
|
|
### Discussion
|
|
|
|
Details.
|
|
""")
|
|
}
|
|
}
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(HoverRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 0, utf16index: 7)))
|
|
|
|
XCTAssertNil(resp)
|
|
}
|
|
}
|
|
|
|
func testHoverNameEscaping() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
|
|
sk.send(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: DocumentURI(url),
|
|
language: .swift,
|
|
version: 1,
|
|
text: """
|
|
/// this is **bold** documentation
|
|
func test(_ a: Int, _ b: Int) { }
|
|
/// this is *italic* documentation
|
|
func *%*(lhs: String, rhs: String) { }
|
|
""")))
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(HoverRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 1, utf16index: 7)))
|
|
|
|
XCTAssertNotNil(resp)
|
|
if let hover = resp {
|
|
XCTAssertNil(hover.range)
|
|
guard case .markupContent(let content) = hover.contents else {
|
|
XCTFail("hover.contents is not .markupContents")
|
|
return
|
|
}
|
|
XCTAssertEqual(content.kind, .markdown)
|
|
XCTAssertEqual(content.value, ##"""
|
|
# test(\_:\_:)
|
|
```
|
|
func test(_ a: Int, _ b: Int)
|
|
```
|
|
|
|
this is **bold** documentation
|
|
"""##)
|
|
}
|
|
}
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(HoverRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 3, utf16index: 7)))
|
|
|
|
XCTAssertNotNil(resp)
|
|
if let hover = resp {
|
|
XCTAssertNil(hover.range)
|
|
guard case .markupContent(let content) = hover.contents else {
|
|
XCTFail("hover.contents is not .markupContents")
|
|
return
|
|
}
|
|
XCTAssertEqual(content.kind, .markdown)
|
|
XCTAssertEqual(content.value, ##"""
|
|
# \*%\*(\_:\_:)
|
|
```
|
|
func *%* (lhs: String, rhs: String)
|
|
```
|
|
|
|
this is *italic* documentation
|
|
"""##)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testDocumentSymbolHighlight() {
|
|
let url = URL(fileURLWithPath: "/\(#function)/a.swift")
|
|
let uri = DocumentURI(url)
|
|
|
|
sk.send(DidOpenTextDocumentNotification(textDocument: TextDocumentItem(
|
|
uri: uri,
|
|
language: .swift,
|
|
version: 1,
|
|
text: """
|
|
func test() {
|
|
let a = 1
|
|
let b = 2
|
|
let ccc = 3
|
|
_ = b
|
|
_ = ccc + ccc
|
|
}
|
|
""")))
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(DocumentHighlightRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 0, utf16index: 0)))
|
|
XCTAssertEqual(resp?.count, 0)
|
|
}
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(DocumentHighlightRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 1, utf16index: 6)))
|
|
XCTAssertEqual(resp?.count, 1)
|
|
if let highlight = resp?.first {
|
|
XCTAssertEqual(highlight.range.lowerBound.line, 1)
|
|
XCTAssertEqual(highlight.range.lowerBound.utf16index, 6)
|
|
XCTAssertEqual(highlight.range.upperBound.line, 1)
|
|
XCTAssertEqual(highlight.range.upperBound.utf16index, 7)
|
|
}
|
|
}
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(DocumentHighlightRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 2, utf16index: 6)))
|
|
XCTAssertEqual(resp?.count, 2)
|
|
if let highlight = resp?.first {
|
|
XCTAssertEqual(highlight.range.lowerBound.line, 2)
|
|
XCTAssertEqual(highlight.range.lowerBound.utf16index, 6)
|
|
XCTAssertEqual(highlight.range.upperBound.line, 2)
|
|
XCTAssertEqual(highlight.range.upperBound.utf16index, 7)
|
|
}
|
|
if let highlight = resp?.dropFirst().first {
|
|
XCTAssertEqual(highlight.range.lowerBound.line, 4)
|
|
XCTAssertEqual(highlight.range.lowerBound.utf16index, 6)
|
|
XCTAssertEqual(highlight.range.upperBound.line, 4)
|
|
XCTAssertEqual(highlight.range.upperBound.utf16index, 7)
|
|
}
|
|
}
|
|
|
|
do {
|
|
let resp = try! sk.sendSync(DocumentHighlightRequest(
|
|
textDocument: TextDocumentIdentifier(url),
|
|
position: Position(line: 3, utf16index: 6)))
|
|
XCTAssertEqual(resp?.count, 3)
|
|
if let highlight = resp?.first {
|
|
XCTAssertEqual(highlight.range.lowerBound.line, 3)
|
|
XCTAssertEqual(highlight.range.lowerBound.utf16index, 6)
|
|
XCTAssertEqual(highlight.range.upperBound.line, 3)
|
|
XCTAssertEqual(highlight.range.upperBound.utf16index, 9)
|
|
}
|
|
if let highlight = resp?.dropFirst().first {
|
|
XCTAssertEqual(highlight.range.lowerBound.line, 5)
|
|
XCTAssertEqual(highlight.range.lowerBound.utf16index, 6)
|
|
XCTAssertEqual(highlight.range.upperBound.line, 5)
|
|
XCTAssertEqual(highlight.range.upperBound.utf16index, 9)
|
|
}
|
|
if let highlight = resp?.dropFirst(2).first {
|
|
XCTAssertEqual(highlight.range.lowerBound.line, 5)
|
|
XCTAssertEqual(highlight.range.lowerBound.utf16index, 12)
|
|
XCTAssertEqual(highlight.range.upperBound.line, 5)
|
|
XCTAssertEqual(highlight.range.upperBound.utf16index, 15)
|
|
}
|
|
}
|
|
}
|
|
|
|
func testEditorPlaceholderParsing() {
|
|
var text = "<#basic placeholder" +
|
|
"#>" // Need to end this in another line so Xcode doesn't treat it as a real placeholder
|
|
var data = EditorPlaceholder(text)
|
|
XCTAssertNotNil(data)
|
|
if let data = data {
|
|
XCTAssertEqual(data, .basic("basic placeholder"))
|
|
XCTAssertEqual(data.displayName, "basic placeholder")
|
|
}
|
|
text = "<#T##x: Int##Int" +
|
|
"#>"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNotNil(data)
|
|
if let data = data {
|
|
XCTAssertEqual(data, .typed(displayName: "x: Int", type: "Int", typeForExpansion: "Int"))
|
|
XCTAssertEqual(data.displayName, "x: Int")
|
|
}
|
|
text = "<#T##x: Int##Blah##()->Int" +
|
|
"#>"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNotNil(data)
|
|
if let data = data {
|
|
XCTAssertEqual(data, .typed(displayName: "x: Int", type: "Blah", typeForExpansion: "()->Int"))
|
|
XCTAssertEqual(data.displayName, "x: Int")
|
|
}
|
|
text = "<#T##Int" +
|
|
"#>"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNotNil(data)
|
|
if let data = data {
|
|
XCTAssertEqual(data, .typed(displayName: "Int", type: "Int", typeForExpansion: "Int"))
|
|
XCTAssertEqual(data.displayName, "Int")
|
|
}
|
|
text = "<#foo"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNil(data)
|
|
text = " <#foo"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNil(data)
|
|
text = "foo"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNil(data)
|
|
text = "foo#>"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNil(data)
|
|
text = "<#foo#"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNil(data)
|
|
text = " <#foo" +
|
|
"#>"
|
|
data = EditorPlaceholder(text)
|
|
XCTAssertNil(data)
|
|
}
|
|
}
|