Skip to content

Commit 3b21e68

Browse files
committed
classify symbols, not files and add extension symbols to base declaration
1 parent b10f39d commit 3b21e68

2 files changed

Lines changed: 52 additions & 32 deletions

File tree

Core/Sources/Service/MultiFileContext/MultiFileContextManager.swift

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -52,21 +52,45 @@ class MultiFileContextManager {
5252
var result: [String: SymbolContent] = [:]
5353

5454
for file in fileContents {
55-
guard let fileURL = URL(string: file.fileURL) else { continue }
56-
do {
57-
let sourceFile = Parser.parse(source: file.content)
58-
let converter = SourceLocationConverter(fileName: file.fileURL, tree: sourceFile)
59-
let collector = DeclarationCollector(sourceLocationConverter: converter, sourceText: file.content)
60-
collector.walk(sourceFile)
61-
result[file.fileName] = file.mapToSymbolContent(symbols: collector.symbols)
62-
} catch {
63-
print("SwiftSyntax parse failed for \(file.fileURL):", error)
55+
let sourceFile = Parser.parse(source: file.content)
56+
let converter = SourceLocationConverter(fileName: file.fileURL, tree: sourceFile)
57+
let collector = DeclarationCollector(sourceLocationConverter: converter, sourceText: file.content)
58+
collector.walk(sourceFile)
59+
var symbols: [SymbolContent] = collector.symbols.map { symbol in
60+
SymbolContent(fileURL: file.fileURL, content: file.content, symbol: symbol)
61+
}
62+
mergeExtensionsIntoBaseDeclarations(&symbols)
63+
for symbol in symbols {
64+
result[symbol.symbol.name] = symbol
6465
}
6566
}
6667

6768
return result
6869
}
6970

71+
private func mergeExtensionsIntoBaseDeclarations(_ symbols: inout [SymbolContent]) {
72+
var indexesToRemove: [Int] = []
73+
74+
for (index, symbol) in symbols.enumerated() {
75+
guard symbol.symbol.kind == .extensionWord else { continue }
76+
77+
if let targetIndex = symbols.firstIndex(where: {
78+
$0.symbol.name == symbol.symbol.name &&
79+
$0.symbol.kind != .extensionWord
80+
}) {
81+
var target = symbols[targetIndex]
82+
target.symbol.extensions.append(symbol)
83+
symbols[targetIndex] = target
84+
85+
indexesToRemove.append(index)
86+
}
87+
}
88+
89+
for index in indexesToRemove.sorted(by: >) {
90+
symbols.remove(at: index)
91+
}
92+
}
93+
7094
}
7195

7296
struct FileContent {
@@ -80,16 +104,10 @@ struct FileContent {
80104
}
81105
}
82106

83-
extension FileContent {
84-
func mapToSymbolContent(symbols: [SymbolInfo]) -> SymbolContent {
85-
SymbolContent(fileURL: fileURL, content: content, symbols: symbols)
86-
}
87-
}
88-
89107
struct SymbolContent {
90108
let fileURL: String
91109
let content: String
92-
let symbols: [SymbolInfo]
110+
var symbol: SymbolInfo
93111
}
94112

95113
enum ClassificationKeywords: String {
@@ -107,10 +125,11 @@ enum ClassificationKeywords: String {
107125

108126
struct SymbolInfo {
109127
let name: String
110-
let kind: String
128+
let kind: ClassificationKeywords
111129
let startLine: Int
112130
let endLine: Int
113131
let content: String
132+
var extensions: [SymbolContent] = []
114133
}
115134

116135
import SwiftSyntax
@@ -133,58 +152,57 @@ class DeclarationCollector: SyntaxVisitor {
133152
// }
134153

135154
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
136-
recordSymbol(name: node.name.text, kind: ClassificationKeywords.classWord.rawValue, node: node)
155+
recordSymbol(name: node.name.text, kind: ClassificationKeywords.classWord, node: node)
137156
return .skipChildren
138157
}
139158

140159
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
141-
recordSymbol(name: node.name.text, kind: ClassificationKeywords.structWord.rawValue, node: node)
160+
recordSymbol(name: node.name.text, kind: ClassificationKeywords.structWord, node: node)
142161
return .skipChildren
143162
}
144163

145164
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
146-
recordSymbol(name: node.name.text, kind: ClassificationKeywords.enumWord.rawValue, node: node)
165+
recordSymbol(name: node.name.text, kind: ClassificationKeywords.enumWord, node: node)
147166
return .skipChildren
148167
}
149168

150169
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
151-
recordSymbol(name: node.name.text, kind: ClassificationKeywords.protocolWord.rawValue, node: node)
170+
recordSymbol(name: node.name.text, kind: ClassificationKeywords.protocolWord, node: node)
152171
return .skipChildren
153172
}
154173

155174
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
156-
recordSymbol(name: node.name.text, kind: ClassificationKeywords.actorWord.rawValue, node: node)
175+
recordSymbol(name: node.name.text, kind: ClassificationKeywords.actorWord, node: node)
157176
return .skipChildren
158177
}
159178

160179
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
161-
recordSymbol(name: node.name.text, kind: ClassificationKeywords.funcWord.rawValue, node: node)
180+
recordSymbol(name: node.name.text, kind: ClassificationKeywords.funcWord, node: node)
162181
return .skipChildren
163182
}
164183

165184
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
166185
guard let binding = node.bindings.first,
167-
let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else {
186+
let pattern = binding.pattern.as(IdentifierPatternSyntax.self),
187+
let keyword: ClassificationKeywords = ClassificationKeywords(rawValue: node.bindingSpecifier.text) else {
168188
return .skipChildren
169189
}
170-
171-
let keyword = node.bindingSpecifier.text // "let" or "var"
172190
recordSymbol(name: pattern.identifier.text, kind: keyword, node: node)
173191
return .skipChildren
174192
}
175193

176194
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
177195
let name = node.extendedType.trimmedDescription
178-
recordSymbol(name: name, kind: ClassificationKeywords.extensionWord.rawValue, node: node)
196+
recordSymbol(name: name, kind: ClassificationKeywords.extensionWord, node: node)
179197
return .skipChildren
180198
}
181199

182200
override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind {
183-
recordSymbol(name: node.name.text, kind: ClassificationKeywords.typealiasWord.rawValue, node: node)
201+
recordSymbol(name: node.name.text, kind: ClassificationKeywords.typealiasWord, node: node)
184202
return .skipChildren
185203
}
186204

187-
private func recordSymbol(name: String, kind: String, node: SyntaxProtocol) {
205+
private func recordSymbol(name: String, kind: ClassificationKeywords, node: SyntaxProtocol) {
188206
let startLoc = sourceLocationConverter.location(for: node.positionAfterSkippingLeadingTrivia)
189207
let endLoc = sourceLocationConverter.location(for: node.endPositionBeforeTrailingTrivia)
190208
let startLineIndex = startLoc.line - 1

Core/Tests/ServiceTests/MultiFileContextManagerTests.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ class MultiFileContextManagerTests: XCTestCase {
2727

2828
func testClassifyingCode() async {
2929
let sut = sut
30-
let classifiedFiles = await sut.classifyContentWithinFile()
30+
let classifiedSymbols = await sut.classifyContentWithinFile()
3131
// symbols
32-
XCTAssertNotEqual(classifiedFiles.count, 0)
32+
XCTAssertNotEqual(classifiedSymbols.count, 0)
3333
}
3434
}
3535

@@ -39,7 +39,9 @@ class WorkspaceProviderMock: WorkspaceProvider {
3939
// let workspaceURL = URL(fileURLWithPath: "/Users/christopherknapp/repos/CopilotForXcode-Fork/Copilot for Xcode.xcworkspace")
4040
// let workspaceURL = URL(fileURLWithPath: "/Users/christopherknapp/repos/clean-architecture-swiftui-fork")
4141
// let workspaceURL = URL(fileURLWithPath: "/Users/christopherknapp/repos/ios-minttv")
42-
let workspaceURL = URL(fileURLWithPath: "/Users/christopherknapp/repos/ios-minttv/Pod/Classes/Twitch/TwitchEndpoint.swift")
42+
// let workspaceURL = URL(fileURLWithPath: "/Users/christopherknapp/repos/ios-minttv/Pod/Classes/Twitch/TwitchEndpoint.swift")
43+
// let workspaceURL = URL(fileURLWithPath: "/Users/christopherknapp/repos/ios-minttv/Pod/Classes/Twitch/Chat/IRC")
44+
let workspaceURL = URL(fileURLWithPath: "/Users/christopherknapp/repos/ios-minttv/Pod/Classes/Twitch/Chat/Presentation/View")
4345
return Workspace(workspaceURL: workspaceURL)
4446
}
4547
}

0 commit comments

Comments
 (0)