Skip to content

Commit 224900d

Browse files
committed
Show container name as the detail of a call hierarchy item
The container name, showing the class a method is defined on, is more useful than the module name.
1 parent 3e51f70 commit 224900d

File tree

2 files changed

+76
-19
lines changed

2 files changed

+76
-19
lines changed

Sources/SourceKitLSP/SourceKitLSPServer.swift

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2039,14 +2039,14 @@ extension SourceKitLSPServer {
20392039

20402040
private func indexToLSPCallHierarchyItem(
20412041
symbol: Symbol,
2042-
moduleName: String?,
2042+
containerName: String?,
20432043
location: Location
20442044
) -> CallHierarchyItem {
20452045
CallHierarchyItem(
20462046
name: symbol.name,
20472047
kind: symbol.kind.asLspSymbolKind(),
20482048
tags: nil,
2049-
detail: moduleName,
2049+
detail: containerName,
20502050
uri: location.uri,
20512051
range: location.range,
20522052
selectionRange: location.range,
@@ -2077,7 +2077,7 @@ extension SourceKitLSPServer {
20772077

20782078
// Only return a single call hierarchy item. Returning multiple doesn't make sense because they will all have the
20792079
// same USR (because we query them by USR) and will thus expand to the exact same call hierarchy.
2080-
var callHierarchyItems = usrs.compactMap { (usr) -> CallHierarchyItem? in
2080+
let callHierarchyItems = usrs.compactMap { (usr) -> CallHierarchyItem? in
20812081
guard let definition = index.primaryDefinitionOrDeclarationOccurrence(ofUSR: usr) else {
20822082
return nil
20832083
}
@@ -2086,7 +2086,7 @@ extension SourceKitLSPServer {
20862086
}
20872087
return self.indexToLSPCallHierarchyItem(
20882088
symbol: definition.symbol,
2089-
moduleName: definition.location.moduleName,
2089+
containerName: definition.containerName,
20902090
location: location
20912091
)
20922092
}.sorted(by: { Location(uri: $0.uri, range: $0.range) < Location(uri: $1.uri, range: $1.range) })
@@ -2143,7 +2143,7 @@ extension SourceKitLSPServer {
21432143
return CallHierarchyIncomingCall(
21442144
from: indexToLSPCallHierarchyItem(
21452145
symbol: related.symbol,
2146-
moduleName: definitionSymbolLocation?.moduleName,
2146+
containerName: definition?.containerName,
21472147
location: definitionLocation ?? location // Use occurrence location as fallback
21482148
),
21492149
fromRanges: [location.range]
@@ -2174,7 +2174,7 @@ extension SourceKitLSPServer {
21742174
return CallHierarchyOutgoingCall(
21752175
to: indexToLSPCallHierarchyItem(
21762176
symbol: occurrence.symbol,
2177-
moduleName: definitionSymbolLocation?.moduleName,
2177+
containerName: definition?.containerName,
21782178
location: definitionLocation ?? location // Use occurrence location as fallback
21792179
),
21802180
fromRanges: [location.range]
@@ -2472,14 +2472,28 @@ extension IndexSymbolKind {
24722472
}
24732473
}
24742474

2475+
fileprivate extension IndexSymbolKind {
2476+
/// Whether this symbol should be considered as a container in `SymbolOccurrence.containerName`
2477+
var isContainer: Bool {
2478+
switch self {
2479+
case .module, .namespace, .enum, .struct, .class, .protocol, .extension:
2480+
return true
2481+
case .unknown, .namespaceAlias, .macro, .union, .typealias, .function, .variable, .field, .enumConstant,
2482+
.instanceMethod, .classMethod, .staticMethod, .instanceProperty, .classProperty, .staticProperty, .constructor,
2483+
.destructor, .conversionFunction, .parameter, .using, .concept, .commentTag:
2484+
return false
2485+
}
2486+
}
2487+
}
2488+
24752489
extension SymbolOccurrence {
24762490
/// Get the name of the symbol that is a parent of this symbol, if one exists
2477-
func getContainerName() -> String? {
2491+
var containerName: String? {
24782492
let containers = relations.filter { $0.roles.contains(.childOf) }
24792493
if containers.count > 1 {
24802494
logger.fault("Expected an occurrence to a child of at most one symbol, not multiple")
24812495
}
2482-
return containers.sorted().first?.symbol.name
2496+
return containers.filter(\.symbol.kind.isContainer).sorted().first?.symbol.name
24832497
}
24842498
}
24852499

@@ -2547,7 +2561,7 @@ extension WorkspaceSymbolItem {
25472561
kind: symbolOccurrence.symbol.kind.asLspSymbolKind(),
25482562
deprecated: nil,
25492563
location: symbolLocation,
2550-
containerName: symbolOccurrence.getContainerName()
2564+
containerName: symbolOccurrence.containerName
25512565
)
25522566
)
25532567
}

Tests/SourceKitLSPTests/CallHierarchyTests.swift

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ final class CallHierarchyTests: XCTestCase {
9090
func item(
9191
_ name: String,
9292
_ kind: SymbolKind,
93-
detail: String = "test",
93+
detail: String? = nil,
9494
usr: String,
9595
at position: Position
9696
) -> CallHierarchyItem {
@@ -215,7 +215,7 @@ final class CallHierarchyTests: XCTestCase {
215215
name: "foo",
216216
kind: .method,
217217
tags: nil,
218-
detail: "",
218+
detail: "FilePathIndex",
219219
uri: try project.uri(for: "lib.cpp"),
220220
range: try Range(project.position(of: "2️⃣", in: "lib.cpp")),
221221
selectionRange: try Range(project.position(of: "2️⃣", in: "lib.cpp")),
@@ -255,7 +255,7 @@ final class CallHierarchyTests: XCTestCase {
255255
name: "testFunc(x:)",
256256
kind: .function,
257257
tags: nil,
258-
detail: "test", // test is the module name because the file is called test.swift
258+
detail: nil,
259259
uri: project.fileURI,
260260
range: Range(project.positions["2️⃣"]),
261261
selectionRange: Range(project.positions["2️⃣"]),
@@ -297,7 +297,7 @@ final class CallHierarchyTests: XCTestCase {
297297
name: "getter:testVar",
298298
kind: .function,
299299
tags: nil,
300-
detail: "test", // test is the module name because the file is called test.swift
300+
detail: nil,
301301
uri: project.fileURI,
302302
range: Range(project.positions["2️⃣"]),
303303
selectionRange: Range(project.positions["2️⃣"]),
@@ -339,7 +339,7 @@ final class CallHierarchyTests: XCTestCase {
339339
name: "testFunc()",
340340
kind: .function,
341341
tags: nil,
342-
detail: "test", // test is the module name because the file is called test.swift
342+
detail: nil,
343343
uri: project.fileURI,
344344
range: Range(project.positions["2️⃣"]),
345345
selectionRange: Range(project.positions["2️⃣"]),
@@ -355,7 +355,7 @@ final class CallHierarchyTests: XCTestCase {
355355
name: "testFunc()",
356356
kind: .function,
357357
tags: nil,
358-
detail: "test", // test is the module name because the file is called test.swift
358+
detail: nil,
359359
uri: project.fileURI,
360360
range: Range(project.positions["2️⃣"]),
361361
selectionRange: Range(project.positions["2️⃣"]),
@@ -396,7 +396,7 @@ final class CallHierarchyTests: XCTestCase {
396396
name: "getter:foo",
397397
kind: .function,
398398
tags: nil,
399-
detail: "test", // test is the module name because the file is called test.swift
399+
detail: nil,
400400
uri: project.fileURI,
401401
range: Range(project.positions["1️⃣"]),
402402
selectionRange: Range(project.positions["1️⃣"]),
@@ -436,7 +436,7 @@ final class CallHierarchyTests: XCTestCase {
436436
name: "testFunc()",
437437
kind: .function,
438438
tags: nil,
439-
detail: "test", // test is the module name because the file is called test.swift
439+
detail: nil,
440440
uri: project.fileURI,
441441
range: Range(project.positions["1️⃣"]),
442442
selectionRange: Range(project.positions["1️⃣"]),
@@ -485,7 +485,7 @@ final class CallHierarchyTests: XCTestCase {
485485
name: "test(proto:)",
486486
kind: .function,
487487
tags: nil,
488-
detail: "test", // test is the module name because the file is called test.swift
488+
detail: nil,
489489
uri: project.fileURI,
490490
range: Range(project.positions["2️⃣"]),
491491
selectionRange: Range(project.positions["2️⃣"]),
@@ -534,7 +534,7 @@ final class CallHierarchyTests: XCTestCase {
534534
name: "test(base:)",
535535
kind: .function,
536536
tags: nil,
537-
detail: "test", // test is the module name because the file is called test.swift
537+
detail: nil,
538538
uri: project.fileURI,
539539
range: Range(project.positions["2️⃣"]),
540540
selectionRange: Range(project.positions["2️⃣"]),
@@ -548,4 +548,47 @@ final class CallHierarchyTests: XCTestCase {
548548
]
549549
)
550550
}
551+
552+
func testCallHierarchyContainsContainerNameAsDetail() async throws {
553+
let project = try await IndexedSingleSwiftFileTestProject(
554+
"""
555+
class MyClass {
556+
func 1️⃣foo() {
557+
2️⃣bar()
558+
}
559+
}
560+
func 3️⃣bar() {
561+
}
562+
"""
563+
)
564+
let prepare = try await project.testClient.send(
565+
CallHierarchyPrepareRequest(
566+
textDocument: TextDocumentIdentifier(project.fileURI),
567+
position: project.positions["3️⃣"]
568+
)
569+
)
570+
let initialItem = try XCTUnwrap(prepare?.only)
571+
let calls = try await project.testClient.send(CallHierarchyIncomingCallsRequest(item: initialItem))
572+
XCTAssertEqual(
573+
calls,
574+
[
575+
CallHierarchyIncomingCall(
576+
from: CallHierarchyItem(
577+
name: "foo()",
578+
kind: .method,
579+
tags: nil,
580+
detail: "MyClass",
581+
uri: project.fileURI,
582+
range: Range(project.positions["1️⃣"]),
583+
selectionRange: Range(project.positions["1️⃣"]),
584+
data: .dictionary([
585+
"usr": .string("s:4test7MyClassC3fooyyF"),
586+
"uri": .string(project.fileURI.stringValue),
587+
])
588+
),
589+
fromRanges: [Range(project.positions["2️⃣"])]
590+
)
591+
]
592+
)
593+
}
551594
}

0 commit comments

Comments
 (0)