|
1 | 1 | import Foundation |
2 | 2 | import Workspace |
3 | 3 | import XcodeInspector |
| 4 | +import SwiftSyntax |
| 5 | +import SwiftParser |
4 | 6 |
|
5 | 7 | class MultiFileContextManager { |
6 | | - let workspaceProvider: WorkspaceProvider |
| 8 | + private let workspaceProvider: WorkspaceProvider |
| 9 | + |
| 10 | +// private static let classificationKeywords: [String] = ["class", "struct", "enum", "actor", "protocol", "func", "var", "let"] |
| 11 | +// private static let classificationKeywordsWithSpecialCases: [String] = ["extension", "typealias"] |
7 | 12 |
|
8 | 13 | init(workspaceProvider: WorkspaceProvider) { |
9 | 14 | self.workspaceProvider = workspaceProvider |
@@ -41,9 +46,162 @@ class MultiFileContextManager { |
41 | 46 | } |
42 | 47 | } |
43 | 48 | } |
| 49 | + |
| 50 | + func classifyContentWithinFile() async -> [String: SymbolContent] { |
| 51 | + let fileContents = await readFileContents() |
| 52 | + var result: [String: SymbolContent] = [:] |
| 53 | + |
| 54 | + 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) |
| 64 | + } |
| 65 | + } |
| 66 | + |
| 67 | + return result |
| 68 | + } |
| 69 | + |
44 | 70 | } |
45 | 71 |
|
46 | 72 | struct FileContent { |
47 | 73 | let fileURL: String |
48 | 74 | let content: String |
| 75 | + |
| 76 | + var fileName: String { |
| 77 | + let fileNameWithExtension = String(fileURL.split(separator: "/").last ?? "") |
| 78 | + let fileName: String = fileNameWithExtension.replacingOccurrences(of: ".swift", with: "") |
| 79 | + return fileName |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +extension FileContent { |
| 84 | + func mapToSymbolContent(symbols: [SymbolInfo]) -> SymbolContent { |
| 85 | + SymbolContent(fileURL: fileURL, content: content, symbols: symbols) |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +struct SymbolContent { |
| 90 | + let fileURL: String |
| 91 | + let content: String |
| 92 | + let symbols: [SymbolInfo] |
| 93 | +} |
| 94 | + |
| 95 | +enum ClassificationKeywords: String { |
| 96 | + case classWord = "class" |
| 97 | + case structWord = "struct" |
| 98 | + case enumWord = "enum" |
| 99 | + case actorWord = "actor" |
| 100 | + case protocolWord = "protocol" |
| 101 | + case funcWord = "func" |
| 102 | + case varWord = "var" |
| 103 | + case letWord = "let" |
| 104 | + case extensionWord = "extension" |
| 105 | + case typealiasWord = "typealias" |
| 106 | +} |
| 107 | + |
| 108 | +struct SymbolInfo { |
| 109 | + let name: String |
| 110 | + let kind: String |
| 111 | + let startLine: Int |
| 112 | + let endLine: Int |
| 113 | + let content: String |
| 114 | +} |
| 115 | + |
| 116 | +import SwiftSyntax |
| 117 | +//import SwiftSyntaxParser |
| 118 | + |
| 119 | +class DeclarationCollector: SyntaxVisitor { |
| 120 | + var symbols: [SymbolInfo] = [] |
| 121 | + let sourceLocationConverter: SourceLocationConverter |
| 122 | + let sourceText: String |
| 123 | + |
| 124 | + init(sourceLocationConverter: SourceLocationConverter, sourceText: String) { |
| 125 | + self.sourceLocationConverter = sourceLocationConverter |
| 126 | + self.sourceText = sourceText |
| 127 | + super.init(viewMode: .all) |
| 128 | + } |
| 129 | + |
| 130 | +// override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind { |
| 131 | +// recordSymbol(name: node.name.text, kind: "import", node: node) |
| 132 | +// return .skipChildren |
| 133 | +// } |
| 134 | + |
| 135 | + override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { |
| 136 | + recordSymbol(name: node.name.text, kind: ClassificationKeywords.classWord.rawValue, node: node) |
| 137 | + return .skipChildren |
| 138 | + } |
| 139 | + |
| 140 | + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { |
| 141 | + recordSymbol(name: node.name.text, kind: ClassificationKeywords.structWord.rawValue, node: node) |
| 142 | + return .skipChildren |
| 143 | + } |
| 144 | + |
| 145 | + override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { |
| 146 | + recordSymbol(name: node.name.text, kind: ClassificationKeywords.enumWord.rawValue, node: node) |
| 147 | + return .skipChildren |
| 148 | + } |
| 149 | + |
| 150 | + override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { |
| 151 | + recordSymbol(name: node.name.text, kind: ClassificationKeywords.protocolWord.rawValue, node: node) |
| 152 | + return .skipChildren |
| 153 | + } |
| 154 | + |
| 155 | + override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { |
| 156 | + recordSymbol(name: node.name.text, kind: ClassificationKeywords.actorWord.rawValue, node: node) |
| 157 | + return .skipChildren |
| 158 | + } |
| 159 | + |
| 160 | + override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { |
| 161 | + recordSymbol(name: node.name.text, kind: ClassificationKeywords.funcWord.rawValue, node: node) |
| 162 | + return .skipChildren |
| 163 | + } |
| 164 | + |
| 165 | + override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { |
| 166 | + guard let binding = node.bindings.first, |
| 167 | + let pattern = binding.pattern.as(IdentifierPatternSyntax.self) else { |
| 168 | + return .skipChildren |
| 169 | + } |
| 170 | + |
| 171 | + let keyword = node.bindingSpecifier.text // "let" or "var" |
| 172 | + recordSymbol(name: pattern.identifier.text, kind: keyword, node: node) |
| 173 | + return .skipChildren |
| 174 | + } |
| 175 | + |
| 176 | + override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { |
| 177 | + let name = node.extendedType.trimmedDescription |
| 178 | + recordSymbol(name: name, kind: ClassificationKeywords.extensionWord.rawValue, node: node) |
| 179 | + return .skipChildren |
| 180 | + } |
| 181 | + |
| 182 | + override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { |
| 183 | + recordSymbol(name: node.name.text, kind: ClassificationKeywords.typealiasWord.rawValue, node: node) |
| 184 | + return .skipChildren |
| 185 | + } |
| 186 | + |
| 187 | + private func recordSymbol(name: String, kind: String, node: SyntaxProtocol) { |
| 188 | + let startLoc = sourceLocationConverter.location(for: node.positionAfterSkippingLeadingTrivia) |
| 189 | + let endLoc = sourceLocationConverter.location(for: node.endPositionBeforeTrailingTrivia) |
| 190 | + let startLineIndex = startLoc.line - 1 |
| 191 | + let endLineIndex = endLoc.line - 1 |
| 192 | + |
| 193 | + let lines = sourceText.split(separator: "\n", omittingEmptySubsequences: false) |
| 194 | + |
| 195 | + let contentLines = lines[startLineIndex...min(endLineIndex, lines.count - 1)] |
| 196 | + let content = contentLines.joined(separator: "\n") |
| 197 | + |
| 198 | + let symbol = SymbolInfo( |
| 199 | + name: name, |
| 200 | + kind: kind, |
| 201 | + startLine: startLoc.line, |
| 202 | + endLine: endLoc.line, |
| 203 | + content: content |
| 204 | + ) |
| 205 | + symbols.append(symbol) |
| 206 | + } |
49 | 207 | } |
0 commit comments