Files
sourcekit-lsp/Tests/SourceKitLSPTests/DocumentSymbolTests.swift

809 lines
23 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 XCTest
final class DocumentSymbolTests: SourceKitLSPTestCase {
// MARK: - Tests
func testEmpty() async throws {
try await assertDocumentSymbols("") { positions in [] }
}
func testUnicode1() async throws {
try await assertDocumentSymbols("1️⃣struct 2️⃣Żółć3️⃣ { }4️⃣") { positions in
[
DocumentSymbol(
name: "Żółć",
detail: nil,
kind: .struct,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testUnicode2() async throws {
try await assertDocumentSymbols("1️⃣struct 2️⃣🍰3️⃣ { }4️⃣") { positions in
[
DocumentSymbol(
name: "🍰",
detail: nil,
kind: .struct,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testEnumCase() async throws {
try await assertDocumentSymbols(
"""
1️⃣enum 2️⃣Foo3️⃣ {
case 4️⃣first5️⃣, 6️⃣second7️⃣
}8️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
detail: nil,
kind: .enum,
deprecated: nil,
range: positions["1️⃣"]..<positions["8️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "first",
detail: nil,
kind: .enumMember,
deprecated: nil,
range: positions["4️⃣"]..<positions["5️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
),
DocumentSymbol(
name: "second",
detail: nil,
kind: .enumMember,
deprecated: nil,
range: positions["6️⃣"]..<positions["7️⃣"],
selectionRange: positions["6️⃣"]..<positions["7️⃣"],
children: []
),
]
)
]
}
}
func testEnumCaseWithAssociatedValue() async throws {
try await assertDocumentSymbols(
"""
1️⃣enum 2️⃣Foo3️⃣ {
case 4️⃣first(Int)5️⃣
}6️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
detail: nil,
kind: .enum,
deprecated: nil,
range: positions["1️⃣"]..<positions["6️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "first(_:)",
detail: nil,
kind: .enumMember,
deprecated: nil,
range: positions["4️⃣"]..<positions["5️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
)
]
)
]
}
}
func testEnumCaseWithNamedAssociatedValue() async throws {
try await assertDocumentSymbols(
"""
1️⃣enum 2️⃣Foo3️⃣ {
case 4️⃣first(someName: Int)5️⃣
}6️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
detail: nil,
kind: .enum,
deprecated: nil,
range: positions["1️⃣"]..<positions["6️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "first(someName:)",
detail: nil,
kind: .enumMember,
deprecated: nil,
range: positions["4️⃣"]..<positions["5️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
)
]
)
]
}
}
func testExtension() async throws {
try await assertDocumentSymbols(
"""
// struct ThisIsCommentedOut { }
/* struct ThisOneToo { } */
1️⃣extension 2️⃣Int3️⃣ { }4️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Int",
detail: nil,
kind: .namespace,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testStruct() async throws {
try await assertDocumentSymbols("1️⃣struct 2️⃣Struct3️⃣ { }4️⃣") { positions in
[
DocumentSymbol(
name: "Struct",
detail: nil,
kind: .struct,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testClass() async throws {
try await assertDocumentSymbols("1️⃣class 2️⃣Class3️⃣ { }4️⃣") { positions in
[
DocumentSymbol(
name: "Class",
detail: nil,
kind: .class,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testEnum() async throws {
try await assertDocumentSymbols("1️⃣enum 2️⃣Enum3️⃣ { case 4️⃣enumMember5️⃣ }6️⃣") { positions in
[
DocumentSymbol(
name: "Enum",
detail: nil,
kind: .enum,
deprecated: nil,
range: positions["1️⃣"]..<positions["6️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "enumMember",
detail: nil,
kind: .enumMember,
deprecated: nil,
range: positions["4️⃣"]..<positions["5️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
)
]
)
]
}
}
func testProtocol() async throws {
try await assertDocumentSymbols("1️⃣protocol 2️⃣Interface3️⃣ { 4️⃣func 5️⃣f()6️⃣ }7️⃣") { positions in
[
DocumentSymbol(
name: "Interface",
detail: nil,
kind: .interface,
deprecated: nil,
range: positions["1️⃣"]..<positions["7️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "f()",
detail: nil,
kind: .method,
deprecated: nil,
range: positions["4️⃣"]..<positions["6️⃣"],
selectionRange: positions["5️⃣"]..<positions["6️⃣"],
children: []
)
]
)
]
}
}
func testFunction() async throws {
try await assertDocumentSymbols("1️⃣func 2️⃣function()3️⃣ { }4️⃣") { positions in
[
DocumentSymbol(
name: "function()",
detail: nil,
kind: .function,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testVariable() async throws {
try await assertDocumentSymbols("1️⃣var 2️⃣variable3️⃣ = 04️⃣") { positions in
[
DocumentSymbol(
name: "variable",
detail: nil,
kind: .variable,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testMultiplePatternsInVariable() async throws {
try await assertDocumentSymbols("var 1️⃣varA2️⃣: Int,3️⃣ 4️⃣varB5️⃣ = 06️⃣") { positions in
[
DocumentSymbol(
name: "varA",
detail: nil,
kind: .variable,
deprecated: nil,
range: positions["1️⃣"]..<positions["3️⃣"],
selectionRange: positions["1️⃣"]..<positions["2️⃣"],
children: []
),
DocumentSymbol(
name: "varB",
detail: nil,
kind: .variable,
deprecated: nil,
range: positions["4️⃣"]..<positions["6️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
),
]
}
}
func testConstant() async throws {
try await assertDocumentSymbols("1️⃣let 2️⃣constant3️⃣ = 04️⃣") { positions in
[
DocumentSymbol(
name: "constant",
detail: nil,
kind: .variable,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testComputedVariable() async throws {
try await assertDocumentSymbols("1️⃣var 2️⃣computedVariable3️⃣: Int { return 0 }4️⃣") { positions in
[
DocumentSymbol(
name: "computedVariable",
detail: nil,
kind: .variable,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testOperatorFunc() async throws {
try await assertDocumentSymbols("1️⃣func 2️⃣+(lhs: Struct, rhs: Struct)3️⃣ { }4️⃣") { positions in
[
DocumentSymbol(
name: "+(lhs:rhs:)",
detail: nil,
kind: .operator,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testPrefixOperatorFunc() async throws {
try await assertDocumentSymbols("1️⃣prefix func 2️⃣-(rhs: Struct)3️⃣ { }4️⃣") { positions in
[
DocumentSymbol(
name: "-(rhs:)",
detail: nil,
kind: .operator,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testGenericFunc() async throws {
try await assertDocumentSymbols("1️⃣func 2️⃣f<3️⃣TypeParameter4️⃣>(type: TypeParameter.Type)5️⃣ { }6️⃣") { positions in
[
DocumentSymbol(
name: "f(type:)",
detail: nil,
kind: .function,
deprecated: nil,
range: positions["1️⃣"]..<positions["6️⃣"],
selectionRange: positions["2️⃣"]..<positions["5️⃣"],
children: [
DocumentSymbol(
name: "TypeParameter",
detail: nil,
kind: .typeParameter,
deprecated: nil,
range: positions["3️⃣"]..<positions["4️⃣"],
selectionRange: positions["3️⃣"]..<positions["4️⃣"],
children: []
)
]
)
]
}
}
func testGenericStruct() async throws {
try await assertDocumentSymbols("1️⃣struct 2️⃣S3️⃣<4️⃣TypeParameter5️⃣> { }6️⃣") { positions in
[
DocumentSymbol(
name: "S",
detail: nil,
kind: .struct,
deprecated: nil,
range: positions["1️⃣"]..<positions["6️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "TypeParameter",
detail: nil,
kind: .typeParameter,
deprecated: nil,
range: positions["4️⃣"]..<positions["5️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
)
]
)
]
}
}
func testClassFunction() async throws {
try await assertDocumentSymbols(
"""
1️⃣class 2️⃣Foo3️⃣ {
4️⃣func 5️⃣method()6️⃣ { }7️⃣
}8️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
detail: nil,
kind: .class,
deprecated: nil,
range: positions["1️⃣"]..<positions["8️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "method()",
detail: nil,
kind: .method,
deprecated: nil,
range: positions["4️⃣"]..<positions["7️⃣"],
selectionRange: positions["5️⃣"]..<positions["6️⃣"],
children: []
)
]
)
]
}
}
func testStaticClassFunction() async throws {
try await assertDocumentSymbols(
"""
1️⃣class 2️⃣Foo3️⃣ {
4️⃣static func 5️⃣staticMethod()6️⃣ { }7️⃣
}8️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
detail: nil,
kind: .class,
deprecated: nil,
range: positions["1️⃣"]..<positions["8️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "staticMethod()",
detail: nil,
kind: .method,
deprecated: nil,
range: positions["4️⃣"]..<positions["7️⃣"],
selectionRange: positions["5️⃣"]..<positions["6️⃣"],
children: []
)
]
)
]
}
}
func testStaticClassProperty() async throws {
try await assertDocumentSymbols(
"""
1️⃣class 2️⃣Foo3️⃣ {
4️⃣var 5️⃣property6️⃣ = 07️⃣
}8️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
detail: nil,
kind: .class,
deprecated: nil,
range: positions["1️⃣"]..<positions["8️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "property",
detail: nil,
kind: .property,
deprecated: nil,
range: positions["4️⃣"]..<positions["7️⃣"],
selectionRange: positions["5️⃣"]..<positions["6️⃣"],
children: []
)
]
)
]
}
}
func testInitializer() async throws {
try await assertDocumentSymbols(
"""
1️⃣class 2️⃣Foo3️⃣ {
4️⃣init()5️⃣ { }6️⃣
}7️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
detail: nil,
kind: .class,
deprecated: nil,
range: positions["1️⃣"]..<positions["7️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "init()",
detail: nil,
kind: .constructor,
deprecated: nil,
range: positions["4️⃣"]..<positions["6️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
)
]
)
]
}
}
func testInitializerWithParameters() async throws {
try await assertDocumentSymbols(
"""
1️⃣class 2️⃣Foo3️⃣ {
4️⃣init(_ first: Int, second: Int)5️⃣ { }6️⃣
}7️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
detail: nil,
kind: .class,
deprecated: nil,
range: positions["1️⃣"]..<positions["7️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "init(_:second:)",
detail: nil,
kind: .constructor,
deprecated: nil,
range: positions["4️⃣"]..<positions["6️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
)
]
)
]
}
}
func testLocalVariable() async throws {
try await assertDocumentSymbols(
"""
1️⃣func 2️⃣f()3️⃣ {
let localConstant = 0
}4️⃣
"""
) { positions in
[
DocumentSymbol(
name: "f()",
detail: nil,
kind: .function,
deprecated: nil,
range: positions["1️⃣"]..<positions["4️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: []
)
]
}
}
func testLocalFunction() async throws {
try await assertDocumentSymbols(
"""
1️⃣func 2️⃣f()3️⃣ {
4️⃣func 5️⃣localFunction()6️⃣ { }7️⃣
}8️⃣
"""
) { positions in
[
DocumentSymbol(
name: "f()",
detail: nil,
kind: .function,
deprecated: nil,
range: positions["1️⃣"]..<positions["8️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "localFunction()",
detail: nil,
kind: .function,
deprecated: nil,
range: positions["4️⃣"]..<positions["7️⃣"],
selectionRange: positions["5️⃣"]..<positions["6️⃣"],
children: []
)
]
)
]
}
}
func testIncludeMarkComments() async throws {
try await assertDocumentSymbols(
"""
1️⃣// MARK: Marker2️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Marker",
kind: .namespace,
range: positions["1️⃣"]..<positions["2️⃣"],
selectionRange: positions["1️⃣"]..<positions["2️⃣"]
)
]
}
try await assertDocumentSymbols(
"""
1️⃣// MARK: - Marker2️⃣
"""
) { positions in
[
DocumentSymbol(
name: "- Marker",
kind: .namespace,
range: positions["1️⃣"]..<positions["2️⃣"],
selectionRange: positions["1️⃣"]..<positions["2️⃣"]
)
]
}
try await assertDocumentSymbols(
"""
1️⃣/* MARK: Marker */2️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Marker",
kind: .namespace,
range: positions["1️⃣"]..<positions["2️⃣"],
selectionRange: positions["1️⃣"]..<positions["2️⃣"]
)
]
}
}
func testNestedMarkComment() async throws {
try await assertDocumentSymbols(
"""
1️⃣struct 2️⃣Foo3️⃣ {
4️⃣// MARK: Marker5️⃣
}6️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
kind: .struct,
range: positions["1️⃣"]..<positions["6️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "Marker",
kind: .namespace,
range: positions["4️⃣"]..<positions["5️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"]
)
]
)
]
}
}
func testNestedMarkCommentFollowedAttachedToChild() async throws {
try await assertDocumentSymbols(
"""
1️⃣struct 2️⃣Foo3️⃣ {
4️⃣// MARK: Marker5️⃣
6️⃣func 7️⃣myFunc()8️⃣ { }9️⃣
}🔟
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
kind: .struct,
range: positions["1️⃣"]..<positions["🔟"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "Marker",
kind: .namespace,
range: positions["4️⃣"]..<positions["5️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"]
),
DocumentSymbol(
name: "myFunc()",
kind: .method,
range: positions["6️⃣"]..<positions["9️⃣"],
selectionRange: positions["7️⃣"]..<positions["8️⃣"],
children: []
),
]
)
]
}
}
func testShowDeinit() async throws {
try await assertDocumentSymbols(
"""
1️⃣class 2️⃣Foo3️⃣ {
4️⃣deinit5️⃣ {
}6️⃣
}7️⃣
"""
) { positions in
[
DocumentSymbol(
name: "Foo",
kind: .class,
range: positions["1️⃣"]..<positions["7️⃣"],
selectionRange: positions["2️⃣"]..<positions["3️⃣"],
children: [
DocumentSymbol(
name: "deinit",
kind: .constructor,
range: positions["4️⃣"]..<positions["6️⃣"],
selectionRange: positions["4️⃣"]..<positions["5️⃣"],
children: []
)
]
)
]
}
}
}
private func assertDocumentSymbols(
_ markedText: String,
_ expectedDocumentSymbols: (DocumentPositions) -> [DocumentSymbol],
file: StaticString = #filePath,
line: UInt = #line
) async throws {
let testClient = try await TestSourceKitLSPClient()
let uri = DocumentURI(for: .swift)
let positions = testClient.openDocument(markedText, uri: uri)
let symbols = try unwrap(try await testClient.send(DocumentSymbolRequest(textDocument: TextDocumentIdentifier(uri))))
XCTAssertEqual(symbols, .documentSymbols(expectedDocumentSymbols(positions)), file: file, line: line)
}