forked from swiftlang/sourcekit-lsp
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDefinitionLocations.swift
More file actions
160 lines (145 loc) · 5.88 KB
/
Copy pathDefinitionLocations.swift
File metadata and controls
160 lines (145 loc) · 5.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2026 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
//
//===----------------------------------------------------------------------===//
package import IndexStoreDB
@_spi(SourceKitLSP) package import LanguageServerProtocol
@_spi(SourceKitLSP) import SKLogging
package import SemanticIndex
/// Converts a location from the symbol index to an LSP location.
///
/// - Parameter location: The symbol index location
/// - Returns: The LSP location
package func indexToLSPLocation(_ location: SymbolLocation) -> Location? {
guard !location.path.isEmpty else { return nil }
return Location(
uri: location.documentUri,
range: Range(
Position(
// 1-based -> 0-based
// Note that we still use max(0, ...) as a fallback if the location is zero.
line: max(0, location.line - 1),
// Technically we would need to convert the UTF-8 column to a UTF-16 column. This would require reading the
// file. In practice they almost always coincide, so we accept the incorrectness here to avoid the file read.
utf16index: max(0, location.utf8Column - 1)
)
)
)
}
/// The result of looking up definition locations for a symbol.
package struct DefinitionLocationsResult {
/// The locations of the symbol's definition.
package let locations: [Location]
/// The occurrences from the index lookup, if any. Can be used by callers to avoid duplicate index lookups.
package let indexOccurrences: [SymbolOccurrence]
package init(locations: [Location], indexOccurrences: [SymbolOccurrence] = []) {
self.locations = locations
self.indexOccurrences = indexOccurrences
}
}
/// Return the locations for jump to definition from the given `SymbolDetails`.
package func definitionLocations(
for symbol: SymbolDetails,
originatorUri: DocumentURI,
index: CheckedIndex?,
languageService: any LanguageService
) async throws -> DefinitionLocationsResult {
// If this symbol is a module then generate a textual interface
if symbol.kind == .module {
// For module symbols, prefer using systemModule information if available
let moduleName: String
let groupName: String?
if let systemModule = symbol.systemModule {
moduleName = systemModule.moduleName
groupName = systemModule.groupName
} else if let name = symbol.name {
moduleName = name
groupName = nil
} else {
return DefinitionLocationsResult(locations: [])
}
let location = try await definitionInInterface(
moduleName: moduleName,
groupName: groupName,
symbolUSR: nil,
originatorUri: originatorUri,
languageService: languageService
)
return DefinitionLocationsResult(locations: [location])
}
// System symbols use generated interface
if symbol.isSystem ?? false, let systemModule = symbol.systemModule {
let location = try await definitionInInterface(
moduleName: systemModule.moduleName,
groupName: systemModule.groupName,
symbolUSR: symbol.usr,
originatorUri: originatorUri,
languageService: languageService
)
return DefinitionLocationsResult(locations: [location])
}
guard let index else {
if let bestLocalDeclaration = symbol.bestLocalDeclaration {
return DefinitionLocationsResult(locations: [bestLocalDeclaration])
}
return DefinitionLocationsResult(locations: [])
}
guard let usr = symbol.usr else { return DefinitionLocationsResult(locations: []) }
logger.info("Performing indexed jump-to-definition with USR \(usr)")
let occurrences = try index.definitionOrDeclarationOccurrences(ofUSR: usr)
if occurrences.isEmpty {
if let bestLocalDeclaration = symbol.bestLocalDeclaration {
return DefinitionLocationsResult(locations: [bestLocalDeclaration])
}
// Fallback: The symbol was not found in the index. This often happens with
// third-party binary frameworks or libraries where indexing data is missing.
// If module info is available, fallback to generating the textual interface.
if let systemModule = symbol.systemModule {
let location = try await definitionInInterface(
moduleName: systemModule.moduleName,
groupName: systemModule.groupName,
symbolUSR: symbol.usr,
originatorUri: originatorUri,
languageService: languageService
)
return DefinitionLocationsResult(locations: [location])
}
}
return DefinitionLocationsResult(
locations: occurrences.compactMap { indexToLSPLocation($0.location) }.sorted(),
indexOccurrences: occurrences
)
}
/// Generate the generated interface for the given module, write it to disk and return the location to which to jump
/// to get to the definition of `symbolUSR`.
///
/// `originatorUri` is the URI of the file from which the definition request is performed. It is used to determine the
/// compiler arguments to generate the generated interface.
package func definitionInInterface(
moduleName: String,
groupName: String?,
symbolUSR: String?,
originatorUri: DocumentURI,
languageService: any LanguageService
) async throws -> Location {
let documentForBuildSettings = originatorUri.buildSettingsFile
guard
let interfaceDetails = try await languageService.openGeneratedInterface(
document: documentForBuildSettings,
moduleName: moduleName,
groupName: groupName,
symbolUSR: symbolUSR
)
else {
throw ResponseError.unknown("Could not generate Swift Interface for \(moduleName)")
}
let position = interfaceDetails.position ?? Position(line: 0, utf16index: 0)
return Location(uri: interfaceDetails.uri, range: Range(position))
}