mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-10-13 10:49:02 +02:00
If the SwiftPM build server is slow to respond, we may get document symbols based on a `SyntaxTreeManager` with fallback build settings, which would not contain the `_test_EverythingUnexpected` experimental feature and which would thus fail. Similar issues existed in other tests as well.
1552 lines
47 KiB
Swift
1552 lines
47 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
@_spi(SourceKitLSP) import LanguageServerProtocol
|
|
import SKLogging
|
|
import SKTestSupport
|
|
import SourceKitLSP
|
|
import SwiftExtensions
|
|
import XCTest
|
|
|
|
final class SwiftCompletionTests: SourceKitLSPTestCase {
|
|
// MARK: - Helpers
|
|
|
|
private var snippetCapabilities = ClientCapabilities(
|
|
textDocument: TextDocumentClientCapabilities(
|
|
completion: TextDocumentClientCapabilities.Completion(
|
|
completionItem: TextDocumentClientCapabilities.Completion.CompletionItem(snippetSupport: true)
|
|
)
|
|
)
|
|
)
|
|
|
|
// MARK: - Tests
|
|
|
|
func testCompletionBasic() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
try await SkipUnless.sourcekitdSupportsFullDocumentationInCompletion()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct S {
|
|
/// Documentation for `abc`.
|
|
///
|
|
/// _More_ documentation for `abc`.
|
|
///
|
|
/// Usage:
|
|
/// ```swift
|
|
/// S().abc
|
|
/// ```
|
|
var abc: Int
|
|
|
|
func test(a: Int) {
|
|
self.1️⃣abc
|
|
2️⃣ }
|
|
|
|
func test(_ b: Int) {
|
|
self.abc
|
|
}
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
let selfDot = try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"]
|
|
)
|
|
)
|
|
|
|
XCTAssertTrue(selfDot.isIncomplete)
|
|
XCTAssertGreaterThanOrEqual(selfDot.items.count, 2)
|
|
let abc = selfDot.items.first { $0.label == "abc" }
|
|
XCTAssertNotNil(abc)
|
|
if let abc = abc {
|
|
XCTAssertEqual(abc.kind, .property)
|
|
XCTAssertEqual(abc.detail, "Int")
|
|
assertMarkdown(
|
|
documentation: abc.documentation,
|
|
expected: """
|
|
Documentation for `abc`.
|
|
|
|
_More_ documentation for `abc`.
|
|
|
|
Usage:
|
|
```swift
|
|
S().abc
|
|
```
|
|
"""
|
|
)
|
|
XCTAssertEqual(abc.filterText, "abc")
|
|
XCTAssertEqual(abc.textEdit, .textEdit(TextEdit(range: Range(positions["1️⃣"]), newText: "abc")))
|
|
XCTAssertEqual(abc.insertText, "abc")
|
|
XCTAssertEqual(abc.insertTextFormat, .plain)
|
|
}
|
|
|
|
for columnOffset in 1...3 {
|
|
let offsetPosition = positions["1️⃣"].adding(columns: columnOffset)
|
|
let inIdent = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: offsetPosition)
|
|
)
|
|
guard let abc = inIdent.items.first(where: { $0.label == "abc" }) else {
|
|
XCTFail("No completion item with label 'abc'")
|
|
return
|
|
}
|
|
|
|
// If we switch to server-side filtering this will change.
|
|
XCTAssertEqual(abc.kind, .property)
|
|
XCTAssertEqual(abc.detail, "Int")
|
|
assertMarkdown(
|
|
documentation: abc.documentation,
|
|
expected: """
|
|
Documentation for `abc`.
|
|
|
|
_More_ documentation for `abc`.
|
|
|
|
Usage:
|
|
```swift
|
|
S().abc
|
|
```
|
|
"""
|
|
)
|
|
XCTAssertEqual(abc.filterText, "abc")
|
|
XCTAssertEqual(abc.textEdit, .textEdit(TextEdit(range: positions["1️⃣"]..<offsetPosition, newText: "abc")))
|
|
XCTAssertEqual(abc.insertText, "abc")
|
|
XCTAssertEqual(abc.insertTextFormat, .plain)
|
|
}
|
|
|
|
let after = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"])
|
|
)
|
|
XCTAssertNotEqual(after, selfDot)
|
|
}
|
|
|
|
func testCompletionSnippetSupport() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct S {
|
|
/// Documentation for `abc`.
|
|
var abc: Int
|
|
|
|
func test(a: Int) {
|
|
self.1️⃣abc
|
|
}
|
|
|
|
func test(_ b: Int) {
|
|
self.2️⃣abc
|
|
}
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
func getTestMethodCompletion(_ position: Position, label: String) async throws -> CompletionItem? {
|
|
let selfDot = try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: position
|
|
)
|
|
)
|
|
return selfDot.items.first { $0.label == label }
|
|
}
|
|
|
|
var test = try await getTestMethodCompletion(positions["1️⃣"], label: "test(a: Int)")
|
|
XCTAssertNotNil(test)
|
|
if let test = test {
|
|
XCTAssertEqual(test.kind, .method)
|
|
XCTAssertEqual(test.detail, "Void")
|
|
XCTAssertEqual(test.filterText, "test(a:)")
|
|
XCTAssertEqual(test.textEdit, .textEdit(TextEdit(range: Range(positions["1️⃣"]), newText: "test(a: ${1:Int})")))
|
|
XCTAssertEqual(test.insertText, "test(a: ${1:Int})")
|
|
XCTAssertEqual(test.insertTextFormat, .snippet)
|
|
}
|
|
|
|
test = try await getTestMethodCompletion(positions["2️⃣"], label: "test(b: Int)")
|
|
XCTAssertNotNil(test)
|
|
if let test = test {
|
|
XCTAssertEqual(test.kind, .method)
|
|
XCTAssertEqual(test.detail, "Void")
|
|
XCTAssertEqual(test.filterText, "test(:)")
|
|
XCTAssertEqual(test.textEdit, .textEdit(TextEdit(range: Range(positions["2️⃣"]), newText: "test(${1:Int})")))
|
|
XCTAssertEqual(test.insertText, "test(${1:Int})")
|
|
XCTAssertEqual(test.insertTextFormat, .snippet)
|
|
}
|
|
}
|
|
|
|
func testCompletionNoSnippetSupport() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct S {
|
|
/// Documentation for `abc`.
|
|
var abc: Int
|
|
|
|
func test(a: Int) {
|
|
self.1️⃣abc
|
|
}
|
|
|
|
func test(_ b: Int) {
|
|
self.2️⃣abc
|
|
}
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
func getTestMethodCompletion(_ position: Position, label: String) async throws -> CompletionItem? {
|
|
let selfDot = try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: position
|
|
)
|
|
)
|
|
return selfDot.items.first { $0.label == label }
|
|
}
|
|
|
|
var test = try await getTestMethodCompletion(positions["1️⃣"], label: "test(a: Int)")
|
|
XCTAssertNotNil(test)
|
|
if let test = test {
|
|
XCTAssertEqual(test.kind, .method)
|
|
XCTAssertEqual(test.detail, "Void")
|
|
XCTAssertEqual(test.filterText, "test(a:)")
|
|
XCTAssertEqual(test.textEdit, .textEdit(TextEdit(range: Range(positions["1️⃣"]), newText: "test(a: )")))
|
|
XCTAssertEqual(test.insertText, "test(a: )")
|
|
XCTAssertEqual(test.insertTextFormat, .plain)
|
|
}
|
|
|
|
test = try await getTestMethodCompletion(positions["2️⃣"], label: "test(b: Int)")
|
|
XCTAssertNotNil(test)
|
|
if let test = test {
|
|
XCTAssertEqual(test.kind, .method)
|
|
XCTAssertEqual(test.detail, "Void")
|
|
XCTAssertEqual(test.filterText, "test(:)")
|
|
XCTAssertEqual(test.textEdit, .textEdit(TextEdit(range: Range(positions["2️⃣"]), newText: "test()")))
|
|
XCTAssertEqual(test.insertText, "test()")
|
|
XCTAssertEqual(test.insertTextFormat, .plain)
|
|
}
|
|
}
|
|
|
|
func testCompletionPositionServerFilter() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
testClient.openDocument("foo", uri: uri)
|
|
|
|
for col in 0...3 {
|
|
let inOrAfterFoo = try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: Position(line: 0, utf16index: col)
|
|
)
|
|
)
|
|
XCTAssertTrue(inOrAfterFoo.isIncomplete)
|
|
XCTAssertFalse(inOrAfterFoo.items.isEmpty)
|
|
}
|
|
|
|
let outOfRange1 = try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: Position(line: 0, utf16index: 4)
|
|
)
|
|
)
|
|
XCTAssertTrue(outOfRange1.isIncomplete)
|
|
|
|
let outOfRange2 = try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: Position(line: 1, utf16index: 0)
|
|
)
|
|
)
|
|
XCTAssertTrue(outOfRange2.isIncomplete)
|
|
}
|
|
|
|
func testCompletionOptional() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct Foo {
|
|
let bar: Int
|
|
}
|
|
let a: Foo? = Foo(bar: 1)
|
|
a1️⃣.2️⃣ba
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
for columnOffset in 0...2 {
|
|
let offsetPosition = positions["2️⃣"].adding(columns: columnOffset)
|
|
let response = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: offsetPosition)
|
|
)
|
|
|
|
guard let item = response.items.first(where: { $0.label.contains("bar") }) else {
|
|
XCTFail("No completion item with label 'bar'")
|
|
return
|
|
}
|
|
XCTAssertEqual(item.filterText, ".bar")
|
|
XCTAssertEqual(
|
|
item.textEdit,
|
|
.textEdit(
|
|
TextEdit(range: positions["1️⃣"]..<offsetPosition, newText: "?.bar")
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
func testCompletionOverride() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
class Base {
|
|
func foo() {}
|
|
}
|
|
class C: Base {
|
|
1️⃣func 2️⃣ // don't delete trailing space in this file
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
let response = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"])
|
|
)
|
|
guard let item = response.items.first(where: { $0.label == "foo()" }) else {
|
|
XCTFail("No completion item with label 'foo()'")
|
|
return
|
|
}
|
|
XCTAssertEqual(item.filterText, "func foo()")
|
|
XCTAssertEqual(
|
|
item.textEdit,
|
|
.textEdit(TextEdit(range: positions["1️⃣"]..<positions["2️⃣"], newText: "override func foo() {\n\n}"))
|
|
)
|
|
}
|
|
|
|
func testCompletionOverrideInNewLine() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
class Base {
|
|
func foo() {}
|
|
}
|
|
class C: Base {
|
|
func
|
|
1️⃣ // don't delete trailing space in this file
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
let response = try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"]
|
|
)
|
|
)
|
|
guard let item = response.items.first(where: { $0.label == "foo()" }) else {
|
|
XCTFail("No completion item with label 'foo()'")
|
|
return
|
|
}
|
|
// If the edit would cross multiple lines, we are currently not replacing any text. It's not technically correct but the best we can do.
|
|
XCTAssertEqual(item.filterText, "foo()")
|
|
XCTAssertEqual(
|
|
item.textEdit,
|
|
.textEdit(TextEdit(range: Range(positions["1️⃣"]), newText: "override func foo() {\n\n}"))
|
|
)
|
|
}
|
|
|
|
func testRefilterAfterIncompleteResults() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct S {
|
|
func fooAbc() {}
|
|
func fooBcd() {}
|
|
func fooCde() {}
|
|
func fooDef() {}
|
|
func fooGoop() {}
|
|
func test() {
|
|
1️⃣ self.f2️⃣cdez
|
|
}
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
assertEqual(
|
|
5,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["2️⃣"],
|
|
context: CompletionContext(triggerKind: .invoked)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
assertEqual(
|
|
3,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["2️⃣"].adding(columns: 1),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
assertEqual(
|
|
2,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["2️⃣"].adding(columns: 2),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
assertEqual(
|
|
1,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["2️⃣"].adding(columns: 3),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
assertEqual(
|
|
0,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["2️⃣"].adding(columns: 4),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
assertEqual(
|
|
2,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["2️⃣"].adding(columns: 2),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
assertEqual(
|
|
1,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["2️⃣"].adding(columns: 3),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
// Trigger kind changed => OK (200 is maxResults since we're outside the member completion)
|
|
assertEqual(
|
|
200,
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"],
|
|
context: CompletionContext(triggerKind: .invoked)
|
|
)
|
|
).items.count
|
|
)
|
|
}
|
|
|
|
func testRefilterAfterIncompleteResultsWithEdits() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct S {
|
|
func fooAbc() {}
|
|
func fooBcd() {}
|
|
func fooCde() {}
|
|
func fooDef() {}
|
|
func fooGoop() {}
|
|
func test() {
|
|
self.f1️⃣z
|
|
}
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
// 'f'
|
|
assertEqual(
|
|
5,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"],
|
|
context: CompletionContext(triggerKind: .invoked)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
// 'fz'
|
|
assertEqual(
|
|
0,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"].adding(columns: 1),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
testClient.send(
|
|
DidChangeTextDocumentNotification(
|
|
textDocument: VersionedTextDocumentIdentifier(uri, version: 1),
|
|
contentChanges: [
|
|
.init(range: positions["1️⃣"]..<(positions["1️⃣"].adding(columns: 1)), text: "A ")
|
|
]
|
|
)
|
|
)
|
|
|
|
// 'fA'
|
|
assertEqual(
|
|
1,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"].adding(columns: 1),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
// 'fA '
|
|
assertEqual(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"].adding(columns: 2),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
).items,
|
|
[]
|
|
)
|
|
|
|
testClient.send(
|
|
DidChangeTextDocumentNotification(
|
|
textDocument: VersionedTextDocumentIdentifier(uri, version: 1),
|
|
contentChanges: [
|
|
.init(range: positions["1️⃣"]..<(positions["1️⃣"].adding(columns: 1)), text: "Ab")
|
|
]
|
|
)
|
|
)
|
|
|
|
// 'fAb'
|
|
assertEqual(
|
|
1,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"].adding(columns: 1),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
testClient.send(
|
|
DidChangeTextDocumentNotification(
|
|
textDocument: VersionedTextDocumentIdentifier(uri, version: 1),
|
|
contentChanges: [
|
|
.init(range: positions["1️⃣"]..<(positions["1️⃣"].adding(columns: 1)), text: "")
|
|
]
|
|
)
|
|
)
|
|
|
|
// 'fb'
|
|
assertEqual(
|
|
2,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"].adding(columns: 1),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
testClient.send(
|
|
DidChangeTextDocumentNotification(
|
|
textDocument: VersionedTextDocumentIdentifier(uri, version: 1),
|
|
contentChanges: [
|
|
.init(range: Range(positions["1️⃣"].adding(columns: 1)), text: "d")
|
|
]
|
|
)
|
|
)
|
|
|
|
// 'fbd'
|
|
assertEqual(
|
|
1,
|
|
countFs(
|
|
try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"].adding(columns: 2),
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
/// Regression test for https://bugs.swift.org/browse/SR-13561 to make sure the a session
|
|
/// close waits for its respective open to finish to prevent a session geting stuck open.
|
|
func testSessionCloseWaitsforOpen() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct S {
|
|
func forSomethingCrazy() {}
|
|
func forSomethingCool() {}
|
|
func test() {
|
|
self.forS1️⃣ome
|
|
}
|
|
func print() {}
|
|
func anotherOne() {
|
|
self.prin2️⃣
|
|
}
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
let forSomeComplete = CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["1️⃣"],
|
|
context: CompletionContext(triggerKind: .invoked)
|
|
)
|
|
let printComplete = CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: positions["2️⃣"],
|
|
context: CompletionContext(triggerKind: .invoked)
|
|
)
|
|
|
|
// Code completion for "self.forSome"
|
|
async let forSomeResult = testClient.send(forSomeComplete)
|
|
|
|
// Code completion for "self.prin", previously could immediately invalidate
|
|
// the previous request.
|
|
async let printResult = testClient.send(printComplete)
|
|
|
|
assertEqual(2, countFs(try await forSomeResult))
|
|
assertEqual(1, try await printResult.items.count)
|
|
|
|
// Try code completion for "self.forSome" again to verify that it still works.
|
|
let result = try await testClient.send(forSomeComplete)
|
|
XCTAssertEqual(2, countFs(result))
|
|
}
|
|
|
|
func testCodeCompleteSwiftPackage() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let project = try await SwiftPMTestProject(
|
|
files: [
|
|
"a.swift": """
|
|
struct A {
|
|
func method(a b: Int) {}
|
|
}
|
|
""",
|
|
"b.swift": """
|
|
func test(a: A) {
|
|
a.1️⃣
|
|
}
|
|
""",
|
|
],
|
|
// We don't want to test behavior based on fallback settings. Increase the buildSettingsTimeout to ensure we always get proper build settings.
|
|
options: .testDefault(buildSettingsTimeout: defaultTimeoutDuration)
|
|
)
|
|
let (uri, positions) = try project.openDocument("b.swift")
|
|
|
|
let testPosition = positions["1️⃣"]
|
|
let results = try await project.testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: testPosition)
|
|
)
|
|
|
|
XCTAssertEqual(
|
|
results.items.clearingUnstableValues,
|
|
[
|
|
CompletionItem(
|
|
label: "method(a: Int)",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: "method(a:)",
|
|
insertText: "method(a: )",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: Range(testPosition),
|
|
newText: "method(a: )"
|
|
)
|
|
)
|
|
),
|
|
CompletionItem(
|
|
label: "self",
|
|
kind: .keyword,
|
|
detail: "A",
|
|
deprecated: false,
|
|
filterText: "self",
|
|
insertText: "self",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(
|
|
TextEdit(range: Range(testPosition), newText: "self")
|
|
)
|
|
),
|
|
]
|
|
)
|
|
}
|
|
|
|
func testTriggerFromIncompleteAfterStartingStringLiteral() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
func foo(_ x: String) {}
|
|
|
|
func test() {
|
|
foo1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
// The following is a pattern that VS Code sends. Make sure we don't return an error.
|
|
// - Insert `()``, changing the line to `foo()``
|
|
// - Invoke code completion after `(`
|
|
// - Insert `""`, changing the line to `foo("")`
|
|
// - Insert `d` inside the string literal, changing the line to `foo("d")`
|
|
// - Ask for completion with the `triggerFromIncompleteCompletions` flag set.
|
|
// Since this isn't actually re-filtering but is a completely new code completion session. When we detect this, we
|
|
// should just start a new session and return the results.
|
|
var position = positions["1️⃣"]
|
|
testClient.send(
|
|
DidChangeTextDocumentNotification(
|
|
textDocument: VersionedTextDocumentIdentifier(uri, version: position.utf16index),
|
|
contentChanges: [TextDocumentContentChangeEvent(range: Range(position), text: "()")]
|
|
)
|
|
)
|
|
position.utf16index += 1
|
|
let initialCompletionResults = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: position)
|
|
)
|
|
// Test that we get the "abc" result which makes VS Code think that we are still in the same completion session when doing hte second completion.
|
|
XCTAssert(initialCompletionResults.items.contains(where: { $0.label == #""abc""# }))
|
|
testClient.send(
|
|
DidChangeTextDocumentNotification(
|
|
textDocument: VersionedTextDocumentIdentifier(uri, version: position.utf16index),
|
|
contentChanges: [TextDocumentContentChangeEvent(range: Range(position), text: "\"\"")]
|
|
)
|
|
)
|
|
position.utf16index += 1
|
|
testClient.send(
|
|
DidChangeTextDocumentNotification(
|
|
textDocument: VersionedTextDocumentIdentifier(uri, version: position.utf16index),
|
|
contentChanges: [TextDocumentContentChangeEvent(range: Range(position), text: "d")]
|
|
)
|
|
)
|
|
let secondCompletionResults = try await testClient.send(
|
|
CompletionRequest(
|
|
textDocument: TextDocumentIdentifier(uri),
|
|
position: position,
|
|
context: CompletionContext(triggerKind: .triggerFromIncompleteCompletions)
|
|
)
|
|
)
|
|
// We shouldn't be getting code completion results for inside the string literal.
|
|
XCTAssert(secondCompletionResults.items.isEmpty)
|
|
}
|
|
|
|
func testNonAsciiCompletionFilter() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct Foo {
|
|
func 👨👩👦👨👩👦() {}
|
|
func test() {
|
|
self.1️⃣👨👩👦2️⃣
|
|
}
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues,
|
|
[
|
|
CompletionItem(
|
|
label: "👨👩👦👨👩👦()",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: "👨👩👦👨👩👦()",
|
|
insertText: "👨👩👦👨👩👦()",
|
|
insertTextFormat: .plain,
|
|
textEdit: .textEdit(TextEdit(range: positions["1️⃣"]..<positions["2️⃣"], newText: "👨👩👦👨👩👦()"))
|
|
)
|
|
]
|
|
)
|
|
}
|
|
|
|
func testIndentTrailingClosureBody() async throws {
|
|
// sourcekitd returns a completion item with an already expanded closure here. Make sure that we add indentation to
|
|
// the body.
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct MyArray {
|
|
func myasync(execute work: () -> Void) {}
|
|
}
|
|
|
|
func test(x: MyArray) {
|
|
x.1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues.filter { $0.label.contains("myasync {") },
|
|
[
|
|
CompletionItem(
|
|
label: "myasync { code }",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: "myasync",
|
|
insertText: #"""
|
|
myasync {
|
|
${1:code}
|
|
}
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: Range(positions["1️⃣"]),
|
|
newText: #"""
|
|
myasync {
|
|
${1:code}
|
|
}
|
|
"""#
|
|
)
|
|
)
|
|
)
|
|
]
|
|
)
|
|
}
|
|
|
|
func testIndentTrailingClosureBodyOnOptional() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct MyArray {
|
|
func myasync(execute work: () -> Void) {}
|
|
}
|
|
|
|
func test(x: MyArray?) {
|
|
x1️⃣.2️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues.filter { $0.label.contains("myasync {") },
|
|
[
|
|
CompletionItem(
|
|
label: "myasync { code }",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: ".myasync",
|
|
insertText: #"""
|
|
?.myasync {
|
|
${1:code}
|
|
}
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: positions["1️⃣"]..<positions["2️⃣"],
|
|
newText: #"""
|
|
?.myasync {
|
|
${1:code}
|
|
}
|
|
"""#
|
|
)
|
|
)
|
|
)
|
|
]
|
|
)
|
|
}
|
|
|
|
func testExpandClosurePlaceholder() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct MyArray {
|
|
func myMap(_ body: (Int) -> Bool) {}
|
|
}
|
|
func test(x: MyArray) {
|
|
x.1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues.filter { $0.label.contains("myMap") },
|
|
[
|
|
CompletionItem(
|
|
label: "myMap(body: (Int) -> Bool)",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: "myMap(:)",
|
|
insertText: #"""
|
|
myMap(${1:{ ${2:Int} in ${3:Bool} \}})
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: Range(positions["1️⃣"]),
|
|
newText: #"""
|
|
myMap(${1:{ ${2:Int} in ${3:Bool} \}})
|
|
"""#
|
|
)
|
|
)
|
|
)
|
|
]
|
|
)
|
|
}
|
|
|
|
func testExpandClosurePlaceholderOnOptional() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct MyArray {
|
|
func myMap(_ body: (Int) -> Bool) {}
|
|
}
|
|
func test(x: MyArray?) {
|
|
x1️⃣.2️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues.filter { $0.label.contains("myMap") },
|
|
[
|
|
CompletionItem(
|
|
label: "myMap(body: (Int) -> Bool)",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: ".myMap(:)",
|
|
insertText: #"""
|
|
?.myMap(${1:{ ${2:Int} in ${3:Bool} \}})
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: positions["1️⃣"]..<positions["2️⃣"],
|
|
newText: #"""
|
|
?.myMap(${1:{ ${2:Int} in ${3:Bool} \}})
|
|
"""#
|
|
)
|
|
)
|
|
)
|
|
]
|
|
)
|
|
}
|
|
|
|
func testExpandMultipleClosurePlaceholders() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct MyArray {
|
|
func myMap(_ body: (Int) -> Bool, _ second: (Int) -> String) {}
|
|
}
|
|
func test(x: MyArray) {
|
|
x.1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues.filter { $0.label.contains("myMap") },
|
|
[
|
|
CompletionItem(
|
|
label: "myMap(body: (Int) -> Bool, second: (Int) -> String)",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: "myMap(::)",
|
|
insertText: #"""
|
|
myMap(${1:{ ${2:Int} in ${3:Bool} \}}, ${4:{ ${5:Int} in ${6:String} \}})
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: Range(positions["1️⃣"]),
|
|
newText: #"""
|
|
myMap(${1:{ ${2:Int} in ${3:Bool} \}}, ${4:{ ${5:Int} in ${6:String} \}})
|
|
"""#
|
|
)
|
|
)
|
|
)
|
|
]
|
|
)
|
|
}
|
|
|
|
func testExpandMultipleClosurePlaceholdersWithLabel() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct MyArray {
|
|
func myMap(_ body: (Int) -> Bool, second: (Int) -> String) {}
|
|
}
|
|
func test(x: MyArray) {
|
|
x.1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues.filter { $0.label.contains("myMap") },
|
|
[
|
|
CompletionItem(
|
|
label: "myMap(body: (Int) -> Bool, second: (Int) -> String)",
|
|
kind: .method,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: "myMap(:second:)",
|
|
insertText: #"""
|
|
myMap(${1:{ ${2:Int} in ${3:Bool} \}}, second: ${4:{ ${5:Int} in ${6:String} \}})
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: Range(positions["1️⃣"]),
|
|
newText: #"""
|
|
myMap(${1:{ ${2:Int} in ${3:Bool} \}}, second: ${4:{ ${5:Int} in ${6:String} \}})
|
|
"""#
|
|
)
|
|
)
|
|
)
|
|
]
|
|
)
|
|
}
|
|
|
|
func testInferIndentationWhenExpandingClosurePlaceholder() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct MyArray {
|
|
func myMap(_ body: (Int) -> Bool) -> Int {
|
|
return 1
|
|
}
|
|
}
|
|
func test(x: MyArray) {
|
|
x.1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.filter { $0.label.contains("myMap") }.clearingUnstableValues,
|
|
[
|
|
CompletionItem(
|
|
label: "myMap(body: (Int) -> Bool)",
|
|
kind: .method,
|
|
detail: "Int",
|
|
deprecated: false,
|
|
filterText: "myMap(:)",
|
|
insertText: #"""
|
|
myMap(${1:{ ${2:Int} in ${3:Bool} \}})
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: Range(positions["1️⃣"]),
|
|
newText: #"""
|
|
myMap(${1:{ ${2:Int} in ${3:Bool} \}})
|
|
"""#
|
|
)
|
|
)
|
|
)
|
|
]
|
|
)
|
|
}
|
|
|
|
func testExpandMacroClosurePlaceholder() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
@freestanding(expression)
|
|
macro myMacroExpr(fn: (Int) -> String) = #externalMacro(module: "", type: "")
|
|
|
|
@freestanding(declaration)
|
|
macro myMacroDecl(fn1: (Int) -> String, fn2: () -> Void) = #externalMacro(module: "", type: "")
|
|
|
|
func test() {
|
|
#1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues.filter { $0.label.contains("myMacro") },
|
|
[
|
|
CompletionItem(
|
|
label: "myMacroDecl(fn1: (Int) -> String, fn2: () -> Void)",
|
|
kind: .value,
|
|
detail: "Declaration Macro",
|
|
deprecated: false,
|
|
filterText: "myMacroDecl(fn1:fn2:)",
|
|
insertText: #"""
|
|
myMacroDecl(fn1: ${1:{ ${2:Int} in ${3:String} \}}, fn2: ${4:{ ${5:Void} \}})
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: Range(positions["1️⃣"]),
|
|
newText: #"""
|
|
myMacroDecl(fn1: ${1:{ ${2:Int} in ${3:String} \}}, fn2: ${4:{ ${5:Void} \}})
|
|
"""#
|
|
)
|
|
)
|
|
),
|
|
CompletionItem(
|
|
label: "myMacroExpr(fn: (Int) -> String)",
|
|
kind: .value,
|
|
detail: "Void",
|
|
deprecated: false,
|
|
filterText: "myMacroExpr(fn:)",
|
|
insertText: #"""
|
|
myMacroExpr(fn: ${1:{ ${2:Int} in ${3:String} \}})
|
|
"""#,
|
|
insertTextFormat: .snippet,
|
|
textEdit: .textEdit(
|
|
TextEdit(
|
|
range: Range(positions["1️⃣"]),
|
|
newText: #"""
|
|
myMacroExpr(fn: ${1:{ ${2:Int} in ${3:String} \}})
|
|
"""#
|
|
)
|
|
)
|
|
),
|
|
]
|
|
)
|
|
}
|
|
|
|
func testCompletionScoring() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: snippetCapabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct Foo {
|
|
func makeBool() -> Bool { true }
|
|
func makeInt() -> Int { 1 }
|
|
func makeString() -> String { "" }
|
|
}
|
|
func test(foo: Foo) {
|
|
let x: Int = foo.make1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(
|
|
completions.items.clearingUnstableValues.map(\.label),
|
|
["makeInt()", "makeBool()", "makeString()"]
|
|
)
|
|
}
|
|
|
|
func testCompletionItemResolve() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
try await SkipUnless.sourcekitdSupportsFullDocumentationInCompletion()
|
|
|
|
let capabilities = ClientCapabilities(
|
|
textDocument: TextDocumentClientCapabilities(
|
|
completion: TextDocumentClientCapabilities.Completion(
|
|
completionItem: TextDocumentClientCapabilities.Completion.CompletionItem(
|
|
resolveSupport: TextDocumentClientCapabilities.Completion.CompletionItem.ResolveSupportProperties(
|
|
properties: ["documentation"]
|
|
)
|
|
)
|
|
)
|
|
)
|
|
)
|
|
|
|
let testClient = try await TestSourceKitLSPClient(capabilities: capabilities)
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct Foo {
|
|
/// Creates a true value
|
|
func makeBool() -> Bool { true }
|
|
}
|
|
func test(foo: Foo) {
|
|
foo.make1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
let item = try XCTUnwrap(completions.items.only)
|
|
XCTAssertNil(item.documentation)
|
|
let resolvedItem = try await testClient.send(CompletionItemResolveRequest(item: item))
|
|
assertMarkdown(
|
|
documentation: resolvedItem.documentation,
|
|
expected: "Creates a true value"
|
|
)
|
|
}
|
|
|
|
func testCompletionBriefDocumentationFallback() async throws {
|
|
try await SkipUnless.sourcekitdSupportsPlugin()
|
|
try await SkipUnless.sourcekitdSupportsFullDocumentationInCompletion()
|
|
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
|
|
// We test completion for result builder build functions since they don't have full documentation
|
|
// but still have brief documentation.
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
@resultBuilder
|
|
struct AnyBuilder {
|
|
static func 1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
let item = try XCTUnwrap(completions.items.filter { $0.label.contains("buildBlock") }.only)
|
|
assertMarkdown(
|
|
documentation: item.documentation,
|
|
expected: "Required by every result builder to build combined results from statement blocks"
|
|
)
|
|
}
|
|
|
|
func testCallDefaultedArguments() async throws {
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
struct Foo {
|
|
func makeBool(value: Bool = true) -> Bool { value }
|
|
}
|
|
func test(foo: Foo) {
|
|
foo.make1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(completions.items.map(\.insertText), ["makeBool()", "makeBool(value: )"])
|
|
}
|
|
|
|
func testCompletionUsingCompileFlagsTxt() async throws {
|
|
let compileFlags =
|
|
if let defaultSDKPath {
|
|
"""
|
|
-DFOO
|
|
-sdk
|
|
\(defaultSDKPath)
|
|
"""
|
|
} else {
|
|
"-DFOO"
|
|
}
|
|
|
|
let project = try await MultiFileTestProject(
|
|
files: [
|
|
"test.swift": """
|
|
func test() {
|
|
#if FOO
|
|
let myVar: String
|
|
#else
|
|
let myVar: Int
|
|
#endif
|
|
print(myVar1️⃣)
|
|
}
|
|
""",
|
|
"compile_flags.txt": compileFlags,
|
|
]
|
|
)
|
|
let (uri, positions) = try project.openDocument("test.swift")
|
|
let completions = try await project.testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
XCTAssertEqual(completions.items.only?.detail, "String")
|
|
}
|
|
|
|
func testSuggestInMemoryFunctionsFromFilesWithinSameModule() async throws {
|
|
let project = try await SwiftPMTestProject(
|
|
files: [
|
|
"First.swift": """
|
|
myFancyFunc1️⃣
|
|
""",
|
|
"Second.swift": "2️⃣",
|
|
],
|
|
// We don't want to test behavior based on fallback settings. Increase the buildSettingsTimeout to ensure we always get proper build settings.
|
|
options: .testDefault(buildSettingsTimeout: defaultTimeoutDuration)
|
|
)
|
|
|
|
let (secondUri, secondPositions) = try project.openDocument("Second.swift")
|
|
project.testClient.send(
|
|
DidChangeTextDocumentNotification(
|
|
textDocument: VersionedTextDocumentIdentifier(secondUri, version: 2),
|
|
contentChanges: [
|
|
TextDocumentContentChangeEvent(
|
|
range: Range(secondPositions["2️⃣"]),
|
|
text: """
|
|
func myFancyFunction() {}
|
|
"""
|
|
)
|
|
]
|
|
)
|
|
)
|
|
|
|
// Synchronize to make sure that the edit to Second.swift has been processed since opening First.swift and the
|
|
// completion request do not have any dependencies on the edit of Second.swift.
|
|
try await project.testClient.send(SynchronizeRequest())
|
|
let (firstUri, firstPositions) = try project.openDocument("First.swift")
|
|
let completions = try await project.testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(firstUri), position: firstPositions["1️⃣"])
|
|
)
|
|
XCTAssert(completions.items.contains(where: { $0.label.contains("myFancyFunction") }))
|
|
}
|
|
|
|
func testSubscriptCompletions() async throws {
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
func foo(x: [Int: Int]) {
|
|
x[1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
assertContains(completions.items.map(\.label), "[key: Int, default: Int]")
|
|
}
|
|
|
|
func testArgumentCompletionContainsClosingParenthesisIfNotPresentInEditor() async throws {
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
func foo() {
|
|
foo(1️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
let completion = try XCTUnwrap(completions.items.only)
|
|
XCTAssertEqual(completion.textEdit, .textEdit(TextEdit(range: Range(positions["1️⃣"]), newText: ")")))
|
|
}
|
|
|
|
func testArgumentCompletionReplacesExistingClosingParenthesis() async throws {
|
|
// See https://github.com/swiftlang/sourcekit-lsp/issues/782 for why this is important
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
func foo() {
|
|
foo(1️⃣)2️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
let completion = try XCTUnwrap(completions.items.only)
|
|
XCTAssertEqual(completion.textEdit, .textEdit(TextEdit(range: positions["1️⃣"]..<positions["2️⃣"], newText: ")")))
|
|
}
|
|
|
|
func testArgumentCompletionReplacesExistingClosingParenthesisInNestedCall() async throws {
|
|
let testClient = try await TestSourceKitLSPClient()
|
|
let uri = DocumentURI(for: .swift)
|
|
let positions = testClient.openDocument(
|
|
"""
|
|
func foo() {
|
|
foo(foo(1️⃣)2️⃣
|
|
}
|
|
""",
|
|
uri: uri
|
|
)
|
|
|
|
let completions = try await testClient.send(
|
|
CompletionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["1️⃣"])
|
|
)
|
|
let completion = try XCTUnwrap(completions.items.only)
|
|
XCTAssertEqual(completion.textEdit, .textEdit(TextEdit(range: positions["1️⃣"]..<positions["2️⃣"], newText: ")")))
|
|
}
|
|
}
|
|
|
|
private func countFs(_ response: CompletionList) -> Int {
|
|
return response.items.filter { $0.label.hasPrefix("f") }.count
|
|
}
|
|
|
|
private func assertMarkdown(
|
|
documentation: StringOrMarkupContent?,
|
|
expected: String,
|
|
file: StaticString = #filePath,
|
|
line: UInt = #line
|
|
) {
|
|
XCTAssertEqual(
|
|
documentation,
|
|
.markupContent(MarkupContent(kind: .markdown, value: expected)),
|
|
file: file,
|
|
line: line
|
|
)
|
|
}
|
|
|
|
fileprivate extension Position {
|
|
func adding(columns: Int) -> Position {
|
|
return Position(line: line, utf16index: utf16index + columns)
|
|
}
|
|
}
|