From 46dcfbbdfce6c3718fc02cc20957bd61bb94114f Mon Sep 17 00:00:00 2001 From: Christopher Knapp Date: Mon, 8 Sep 2025 21:10:14 +0200 Subject: [PATCH 1/3] add duration in seconds to output file --- .../Data/Entity/StoredSuggestionDTO.swift | 20 +++++++++++++ .../MultiFileContextBenchmarkManager.swift | 29 ++++++++++++++----- .../Entity/RelevantSymbolsSummary.swift | 6 ++++ .../Domain/Entity/SuggestionResponse.swift | 1 + 4 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 Core/Sources/HostApp/Benchmark/Domain/Entity/RelevantSymbolsSummary.swift diff --git a/Core/Sources/HostApp/Benchmark/Data/Entity/StoredSuggestionDTO.swift b/Core/Sources/HostApp/Benchmark/Data/Entity/StoredSuggestionDTO.swift index 4b5731ca..531c7729 100644 --- a/Core/Sources/HostApp/Benchmark/Data/Entity/StoredSuggestionDTO.swift +++ b/Core/Sources/HostApp/Benchmark/Data/Entity/StoredSuggestionDTO.swift @@ -1,3 +1,5 @@ +import Foundation + struct StoredSuggestionDTO: Codable { let fileURL: String let id: String @@ -7,6 +9,7 @@ struct StoredSuggestionDTO: Codable { let createdAt: String let relevantSymbols: [RelevantSymbolsDTO] let model: String + let relevantFileScanningDurationInSeconds: Double? struct CursorPositionDTO: Codable { let line: Int @@ -26,4 +29,21 @@ struct StoredSuggestionDTO: Codable { let endLine: Int let kind: String } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(fileURL, forKey: .fileURL) + try container.encode(id, forKey: .id) + try container.encode(suggestionText, forKey: .suggestionText) + try container.encode(position, forKey: .position) + try container.encode(range, forKey: .range) + try container.encode(createdAt, forKey: .createdAt) + try container.encode(relevantSymbols, forKey: .relevantSymbols) + try container.encode(model, forKey: .model) + + if let value = relevantFileScanningDurationInSeconds { + let rounded = Double(round(100 * value) / 100) + try container.encode(rounded, forKey: .relevantFileScanningDurationInSeconds) + } + } } diff --git a/Core/Sources/HostApp/Benchmark/Data/Manager/MultiFileContextBenchmarkManager.swift b/Core/Sources/HostApp/Benchmark/Data/Manager/MultiFileContextBenchmarkManager.swift index 38346531..e7baee3e 100644 --- a/Core/Sources/HostApp/Benchmark/Data/Manager/MultiFileContextBenchmarkManager.swift +++ b/Core/Sources/HostApp/Benchmark/Data/Manager/MultiFileContextBenchmarkManager.swift @@ -93,10 +93,11 @@ class MultiFileContextBenchmarkManager: BenchmarkManager { let entrypoint = metadata.mapToEntrypoint(prefixing: benchmarkDirectory.path) let content: String = (try? String(contentsOf: entrypoint.fileURL, encoding: .utf8)) ?? "" - let relevantSymbols: [SymbolContent] = await retrieveRelevantSymbolsForFileContent( + let relevantSymbolsSummary: RelevantSymbolsSummary? = await retrieveRelevantSymbolsForFileContent( file: FileContent(fileURL: entrypoint.fileURL.path, content: content), workspace: workspace ) + let relevantSymbols = relevantSymbolsSummary?.symbolsAtLevel.flatMap { $0 } ?? [] let suggestionRequest = SuggestionProvider.SuggestionRequest( fileURL: entrypoint.fileURL, relativePath: entrypoint.fileURL.path.replacingOccurrences(of: benchmarkDirectory.path, with: ""), @@ -122,7 +123,8 @@ class MultiFileContextBenchmarkManager: BenchmarkManager { suggestion: firstSuggestion, fileURL: entrypoint.fileURL, relevantSymbolsFromRequest: relevantSymbols, - model: selectedGenAIModelSubject.value + model: selectedGenAIModelSubject.value, + relevantFileScanningDurationInSeconds: relevantSymbolsSummary?.durationInSeconds ) } catch { return nil @@ -196,10 +198,11 @@ class MultiFileContextBenchmarkManager: BenchmarkManager { let entrypoint = metadata.mapToEntrypoint(prefixing: benchmarkDirectory.path) let content: String = (try? String(contentsOf: entrypoint.fileURL, encoding: .utf8)) ?? "" - let relevantSymbols: [SymbolContent] = await retrieveRelevantSymbolsForFileContent( + let relevantSymbolsSummary: RelevantSymbolsSummary? = await retrieveRelevantSymbolsForFileContent( file: FileContent(fileURL: entrypoint.fileURL.path, content: content), workspace: workspace ) + let relevantSymbols = relevantSymbolsSummary?.symbolsAtLevel.flatMap { $0 } ?? [] let limitedRelevantSymbols = Array(relevantSymbols.prefix(10)) let suggestionRequest = SuggestionRequest( @@ -227,7 +230,8 @@ class MultiFileContextBenchmarkManager: BenchmarkManager { ), fileURL: entrypoint.fileURL, relevantSymbolsFromRequest: relevantSymbols, - model: selectedGenAIModelSubject.value + model: selectedGenAIModelSubject.value, + relevantFileScanningDurationInSeconds: relevantSymbolsSummary?.durationInSeconds ) } catch { print("CK \(error)") @@ -235,16 +239,24 @@ class MultiFileContextBenchmarkManager: BenchmarkManager { } } - private func retrieveRelevantSymbolsForFileContent(file: FileContent, workspace: Workspace) async -> [SymbolContent] { + private func retrieveRelevantSymbolsForFileContent(file: FileContent, workspace: Workspace) async -> RelevantSymbolsSummary? { let multiFileContextManager = MultiFileContextManager( workspaceProvider: ManualWorkspaceProvider(workspace: workspace), parser: SwiftProgrammingLanguageSyntaxParser() ) if isMultiFileEnabledSubject.value { + let start = Date() let symbols = await multiFileContextManager.retrieveRelevantSymbolsForFileContent(file: file, ignoreWithinPaths: ["/Benchmark/"]) - return Array(symbols.values) + let end = Date() + let timeTakenInSeconds = end.timeIntervalSince(start) + return RelevantSymbolsSummary( + symbolsAtLevel: [ + Array(symbols.values) + ], + durationInSeconds: timeTakenInSeconds + ) } else { - return [] + return nil } } @@ -534,7 +546,8 @@ extension SuggestionResponse { ), createdAt: timestamp, relevantSymbols: relevantSymbolsFromRequest.map { $0.toStoredDTO() }, - model: model.id + model: model.id, + relevantFileScanningDurationInSeconds: relevantFileScanningDurationInSeconds ) } } diff --git a/Core/Sources/HostApp/Benchmark/Domain/Entity/RelevantSymbolsSummary.swift b/Core/Sources/HostApp/Benchmark/Domain/Entity/RelevantSymbolsSummary.swift new file mode 100644 index 00000000..d92340e7 --- /dev/null +++ b/Core/Sources/HostApp/Benchmark/Domain/Entity/RelevantSymbolsSummary.swift @@ -0,0 +1,6 @@ +import Service + +struct RelevantSymbolsSummary { + let symbolsAtLevel: [[SymbolContent]] + let durationInSeconds: Double +} diff --git a/Core/Sources/HostApp/Benchmark/Domain/Entity/SuggestionResponse.swift b/Core/Sources/HostApp/Benchmark/Domain/Entity/SuggestionResponse.swift index 93841cd3..50ffb0ef 100644 --- a/Core/Sources/HostApp/Benchmark/Domain/Entity/SuggestionResponse.swift +++ b/Core/Sources/HostApp/Benchmark/Domain/Entity/SuggestionResponse.swift @@ -7,4 +7,5 @@ struct SuggestionResponse { let fileURL: URL let relevantSymbolsFromRequest: [SymbolContent] let model: GenAILanguageModel + let relevantFileScanningDurationInSeconds: Double? } From 52f76af78719bd12bb141d4f200c8585de61b5d9 Mon Sep 17 00:00:00 2001 From: Christopher Knapp Date: Mon, 8 Sep 2025 21:17:11 +0200 Subject: [PATCH 2/3] add relevant symbol count to to output file --- .../Benchmark/Data/Entity/StoredSuggestionDTO.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Core/Sources/HostApp/Benchmark/Data/Entity/StoredSuggestionDTO.swift b/Core/Sources/HostApp/Benchmark/Data/Entity/StoredSuggestionDTO.swift index 531c7729..e553e782 100644 --- a/Core/Sources/HostApp/Benchmark/Data/Entity/StoredSuggestionDTO.swift +++ b/Core/Sources/HostApp/Benchmark/Data/Entity/StoredSuggestionDTO.swift @@ -1,6 +1,6 @@ import Foundation -struct StoredSuggestionDTO: Codable { +struct StoredSuggestionDTO: Encodable { let fileURL: String let id: String let suggestionText: String @@ -8,6 +8,9 @@ struct StoredSuggestionDTO: Codable { let range: CursorRangeDTO let createdAt: String let relevantSymbols: [RelevantSymbolsDTO] + var relevantSymbolCount: Int { + relevantSymbols.count + } let model: String let relevantFileScanningDurationInSeconds: Double? @@ -30,6 +33,12 @@ struct StoredSuggestionDTO: Codable { let kind: String } + enum CodingKeys: String, CodingKey { + case fileURL, id, suggestionText, position, range, createdAt, relevantSymbols, model + case relevantFileScanningDurationInSeconds + case relevantSymbolCount // write, but ignore on decoding + } + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(fileURL, forKey: .fileURL) @@ -39,6 +48,7 @@ struct StoredSuggestionDTO: Codable { try container.encode(range, forKey: .range) try container.encode(createdAt, forKey: .createdAt) try container.encode(relevantSymbols, forKey: .relevantSymbols) + try container.encode(relevantSymbolCount, forKey: .relevantSymbolCount) try container.encode(model, forKey: .model) if let value = relevantFileScanningDurationInSeconds { From c7e8e7989496c7a9fddcf0e5f3864ae4e890ae9c Mon Sep 17 00:00:00 2001 From: Christopher Knapp Date: Tue, 9 Sep 2025 01:30:34 +0200 Subject: [PATCH 3/3] add context level functionality --- .../MultiFileContextBenchmarkManager.swift | 51 ++++++++---- .../Domain/Entity/ContextLevelLimit.swift | 21 +++++ .../Entity/RelevantSymbolsSummary.swift | 6 -- .../Domain/Manager/BenchmarkManager.swift | 8 ++ .../Presentation/View/BenchmarkView.swift | 22 +++++ .../ViewModel/BenchmarkViewModel.swift | 18 ++++ .../Entity/RelevantSymbolsSummary.swift | 4 + .../MultiFileContextManager.swift | 83 ++++++++++++++++++- .../SwiftDeclarationCollector.swift | 23 +++-- 9 files changed, 208 insertions(+), 28 deletions(-) create mode 100644 Core/Sources/HostApp/Benchmark/Domain/Entity/ContextLevelLimit.swift delete mode 100644 Core/Sources/HostApp/Benchmark/Domain/Entity/RelevantSymbolsSummary.swift create mode 100644 Core/Sources/Service/MultiFileContext/Entity/RelevantSymbolsSummary.swift diff --git a/Core/Sources/HostApp/Benchmark/Data/Manager/MultiFileContextBenchmarkManager.swift b/Core/Sources/HostApp/Benchmark/Data/Manager/MultiFileContextBenchmarkManager.swift index e7baee3e..d0214534 100644 --- a/Core/Sources/HostApp/Benchmark/Data/Manager/MultiFileContextBenchmarkManager.swift +++ b/Core/Sources/HostApp/Benchmark/Data/Manager/MultiFileContextBenchmarkManager.swift @@ -29,6 +29,17 @@ class MultiFileContextBenchmarkManager: BenchmarkManager { var selectedGenAIModel: AnyPublisher { selectedGenAIModelSubject.eraseToAnyPublisher() } + + private let contextLevelLimitSubject = CurrentValueSubject(.firstLevel) + var contextLevelLimit: AnyPublisher { + contextLevelLimitSubject.eraseToAnyPublisher() + } + + private var contextFileAmountLimitSubject = CurrentValueSubject(nil) + var contextFileAmountLimit: AnyPublisher { + contextFileAmountLimitSubject.eraseToAnyPublisher() + } + private var cancellables = Set() init(benchmarkSettingsRepository: BenchmarkSettingsRepository) { @@ -239,25 +250,23 @@ class MultiFileContextBenchmarkManager: BenchmarkManager { } } - private func retrieveRelevantSymbolsForFileContent(file: FileContent, workspace: Workspace) async -> RelevantSymbolsSummary? { - let multiFileContextManager = MultiFileContextManager( + private func retrieveRelevantSymbolsForFileContent( + file: FileContent, + workspace: Workspace + ) async -> RelevantSymbolsSummary? { + guard isMultiFileEnabledSubject.value else { return nil } + + let manager = MultiFileContextManager( workspaceProvider: ManualWorkspaceProvider(workspace: workspace), parser: SwiftProgrammingLanguageSyntaxParser() ) - if isMultiFileEnabledSubject.value { - let start = Date() - let symbols = await multiFileContextManager.retrieveRelevantSymbolsForFileContent(file: file, ignoreWithinPaths: ["/Benchmark/"]) - let end = Date() - let timeTakenInSeconds = end.timeIntervalSince(start) - return RelevantSymbolsSummary( - symbolsAtLevel: [ - Array(symbols.values) - ], - durationInSeconds: timeTakenInSeconds - ) - } else { - return nil - } + + return await manager.expandRelevantSymbols( + from: file, + ignoreWithinPaths: ["/Benchmark/"], + maxDepth: contextLevelLimitSubject.value.indexLimit, // e.g. First=0, Second=1, …, nil=no limit + maxFiles: contextFileAmountLimitSubject.value // Int?, nil=no cap + ) } private func applyCodeSuggestion(suggestion: SuggestionBasic.CodeSuggestion, at fileURL: URL) async { @@ -470,6 +479,16 @@ class MultiFileContextBenchmarkManager: BenchmarkManager { currentStates[directory] = currentStatesInDirectory taskStatesSubject.send(currentStates) } + + @MainActor + func saveContextLevelLimit(_ limit: ContextLevelLimit) { + contextLevelLimitSubject.send(limit) + } + + @MainActor + func saveContextFileAmountLimit(_ limit: Int?) { + contextFileAmountLimitSubject.send(limit) + } } extension MetadataDTO { diff --git a/Core/Sources/HostApp/Benchmark/Domain/Entity/ContextLevelLimit.swift b/Core/Sources/HostApp/Benchmark/Domain/Entity/ContextLevelLimit.swift new file mode 100644 index 00000000..6ecaa19a --- /dev/null +++ b/Core/Sources/HostApp/Benchmark/Domain/Entity/ContextLevelLimit.swift @@ -0,0 +1,21 @@ +enum ContextLevelLimit: CaseIterable { + case firstLevel + case secondLevel + case thirdLevel + + var name: String { + switch self { + case .firstLevel: return "First Level" + case .secondLevel: return "Second Level" + case .thirdLevel: return "Third Level" + } + } + + var indexLimit: Int? { + switch self { + case .firstLevel: return 0 + case .secondLevel: return 1 + case .thirdLevel: return 2 + } + } +} diff --git a/Core/Sources/HostApp/Benchmark/Domain/Entity/RelevantSymbolsSummary.swift b/Core/Sources/HostApp/Benchmark/Domain/Entity/RelevantSymbolsSummary.swift deleted file mode 100644 index d92340e7..00000000 --- a/Core/Sources/HostApp/Benchmark/Domain/Entity/RelevantSymbolsSummary.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Service - -struct RelevantSymbolsSummary { - let symbolsAtLevel: [[SymbolContent]] - let durationInSeconds: Double -} diff --git a/Core/Sources/HostApp/Benchmark/Domain/Manager/BenchmarkManager.swift b/Core/Sources/HostApp/Benchmark/Domain/Manager/BenchmarkManager.swift index 3bfe48ac..f04c1583 100644 --- a/Core/Sources/HostApp/Benchmark/Domain/Manager/BenchmarkManager.swift +++ b/Core/Sources/HostApp/Benchmark/Domain/Manager/BenchmarkManager.swift @@ -10,4 +10,12 @@ protocol BenchmarkManager { func changeGenAIModel(to newModel: GenAILanguageModel) @MainActor func updateMultiFileContextState(_ newValue: Bool) + + var contextLevelLimit: AnyPublisher { get } + @MainActor + func saveContextLevelLimit(_ limit: ContextLevelLimit) + + var contextFileAmountLimit: AnyPublisher { get } + @MainActor + func saveContextFileAmountLimit(_ limit: Int?) } diff --git a/Core/Sources/HostApp/Benchmark/Presentation/View/BenchmarkView.swift b/Core/Sources/HostApp/Benchmark/Presentation/View/BenchmarkView.swift index 523c359f..c70f1c13 100644 --- a/Core/Sources/HostApp/Benchmark/Presentation/View/BenchmarkView.swift +++ b/Core/Sources/HostApp/Benchmark/Presentation/View/BenchmarkView.swift @@ -28,6 +28,28 @@ struct BenchmarkView: View { ConfigurationButtonView(module: module) } .padding(.vertical) + + HStack(spacing: 20) { + Spacer() + Picker("Context Level Limit: ", selection: $viewModel.contextLevelLimit) { + ForEach(ContextLevelLimit.allCases, id: \.self) { limit in + Text(limit.name).tag(limit) + } + } + .fixedSize(horizontal: true, vertical: false) + + Text("Context File Amount Limit:") + let numberFormatter: NumberFormatter = { + let f = NumberFormatter() + f.numberStyle = .none + return f + }() + TextField("Optional", value: $viewModel.contextFileAmountLimit, formatter: numberFormatter) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .frame(width: 100) + } + .padding(.bottom) + ForEach(viewModel.benchmarkDirectories, id: \.self) { directory in BenchmarkDirectoryEntryView( directory: directory, diff --git a/Core/Sources/HostApp/Benchmark/Presentation/ViewModel/BenchmarkViewModel.swift b/Core/Sources/HostApp/Benchmark/Presentation/ViewModel/BenchmarkViewModel.swift index 618c6468..f395d7d0 100644 --- a/Core/Sources/HostApp/Benchmark/Presentation/ViewModel/BenchmarkViewModel.swift +++ b/Core/Sources/HostApp/Benchmark/Presentation/ViewModel/BenchmarkViewModel.swift @@ -16,6 +16,18 @@ class BenchmarkViewModel: ObservableObject { Task { await benchmarkManager.changeGenAIModel(to: selectedLanguageModel) } } } + @Published var contextLevelLimit: ContextLevelLimit = .firstLevel { + didSet { + guard oldValue != contextLevelLimit else { return } + Task { await benchmarkManager.saveContextLevelLimit(contextLevelLimit) } + } + } + @Published var contextFileAmountLimit: Int? = nil { + didSet { + guard oldValue != contextFileAmountLimit else { return } + Task { await benchmarkManager.saveContextFileAmountLimit(contextFileAmountLimit) } + } + } private let benchmarkSettingsRepository: BenchmarkSettingsRepository private let benchmarkManager: BenchmarkManager @@ -35,6 +47,12 @@ class BenchmarkViewModel: ObservableObject { benchmarkManager.selectedGenAIModel .assign(to: \.selectedLanguageModel, on: self) .store(in: &cancellables) + benchmarkManager.contextLevelLimit + .assign(to: \.contextLevelLimit, on: self) + .store(in: &cancellables) + benchmarkManager.contextFileAmountLimit + .assign(to: \.contextFileAmountLimit, on: self) + .store(in: &cancellables) } func loadBenchmarkDirectories() { diff --git a/Core/Sources/Service/MultiFileContext/Entity/RelevantSymbolsSummary.swift b/Core/Sources/Service/MultiFileContext/Entity/RelevantSymbolsSummary.swift new file mode 100644 index 00000000..3bad56e0 --- /dev/null +++ b/Core/Sources/Service/MultiFileContext/Entity/RelevantSymbolsSummary.swift @@ -0,0 +1,4 @@ +public struct RelevantSymbolsSummary { + public let symbolsAtLevel: [[SymbolContent]] + public let durationInSeconds: Double +} diff --git a/Core/Sources/Service/MultiFileContext/MultiFileContextManager.swift b/Core/Sources/Service/MultiFileContext/MultiFileContextManager.swift index a79c019d..0caf99cc 100644 --- a/Core/Sources/Service/MultiFileContext/MultiFileContextManager.swift +++ b/Core/Sources/Service/MultiFileContext/MultiFileContextManager.swift @@ -62,6 +62,87 @@ public class MultiFileContextManager { return result } + private func readFile(_ urlString: String) -> FileContent? { + guard let url = URL(string: urlString) else { return nil } + guard let content = try? String(contentsOf: url, encoding: .utf8) else { return nil } + return FileContent(fileURL: urlString, content: content) + } + + public func expandRelevantSymbols( + from root: FileContent, + ignoreWithinPaths: [String] = [], + maxDepth: Int? = nil, + maxFiles: Int? = nil + ) async -> RelevantSymbolsSummary { + @inline(__always) + func isIgnored(_ url: String) -> Bool { + ignoreWithinPaths.contains { url.contains($0) } + } + + let start = Date() + + let level0 = await retrieveRelevantSymbolsForFileContent(file: root, ignoreWithinPaths: ignoreWithinPaths) + var level0Unique: [SymbolContent] = [] + var includedFiles = Set() + for s in level0.values { + if includedFiles.insert(s.fileURL).inserted { + level0Unique.append(s) + } + } + var levels: [[SymbolContent]] = [level0Unique] + + var visited = Set([root.fileURL]) + var depth = 0 + var remainingFiles = maxFiles + + while maxDepth == nil || depth < maxDepth! { + let prev = levels.last ?? [] + var nextURLs = Set(prev.map(\.fileURL)) + nextURLs.subtract(visited) + nextURLs = nextURLs.filter { !isIgnored($0) } + + if nextURLs.isEmpty { break } + + if let remaining = remainingFiles, nextURLs.count > remaining { + nextURLs = Set(nextURLs.prefix(remaining)) + } + + var rawNextLevel: [SymbolContent] = [] + for url in nextURLs { + visited.insert(url) + guard let fc = readFile(url) else { continue } + let dict = await retrieveRelevantSymbolsForFileContent(file: fc, ignoreWithinPaths: ignoreWithinPaths) + rawNextLevel.append(contentsOf: dict.values) + } + + var nextLevel: [SymbolContent] = [] + var seenThisLevel = Set() + for s in rawNextLevel { + guard !includedFiles.contains(s.fileURL) else { continue } + if seenThisLevel.insert(s.fileURL).inserted { + nextLevel.append(s) + } + } + + if nextLevel.isEmpty { break } + + includedFiles.formUnion(nextLevel.map(\.fileURL)) + + levels.append(nextLevel) + depth += 1 + + if remainingFiles != nil { + remainingFiles! -= nextURLs.count + if remainingFiles! <= 0 { break } + } + } + + return RelevantSymbolsSummary( + symbolsAtLevel: levels, + durationInSeconds: Date().timeIntervalSince(start) + ) + } + public func retrieveRelevantSymbolsForFileContent( file: FileContent, ignoreWithinPaths: [String] = [] @@ -171,7 +252,7 @@ extension String { let before = (range.lowerBound == startIndex) ? nil : self[index(before: range.lowerBound)] let after = (range.upperBound == endIndex) ? nil : self[range.upperBound] - let boundaryBefore = before.map { !isIdentChar($0) } ?? true + let boundaryBefore = before.map { !isIdentChar($0) && $0 != "." } ?? true let boundaryAfter = after.map { !isIdentChar($0) } ?? true if boundaryBefore && boundaryAfter { diff --git a/Core/Sources/Service/MultiFileContext/SwiftDeclarationCollector.swift b/Core/Sources/Service/MultiFileContext/SwiftDeclarationCollector.swift index 3b8d9c29..922c864c 100644 --- a/Core/Sources/Service/MultiFileContext/SwiftDeclarationCollector.swift +++ b/Core/Sources/Service/MultiFileContext/SwiftDeclarationCollector.swift @@ -10,43 +10,45 @@ class SwiftDeclarationCollector: SyntaxVisitor { self.sourceText = sourceText super.init(viewMode: .all) } - -// override func visit(_ node: ImportDeclSyntax) -> SyntaxVisitorContinueKind { -// recordSymbol(name: node.name.text, kind: "import", node: node) -// return .skipChildren -// } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { + guard !isPrivateOrFilePrivate(node.modifiers) else { return .skipChildren } recordSymbol(name: node.name.text, kind: ClassificationKeywords.classWord, node: node) return .skipChildren } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + guard !isPrivateOrFilePrivate(node.modifiers) else { return .skipChildren } recordSymbol(name: node.name.text, kind: ClassificationKeywords.structWord, node: node) return .skipChildren } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + guard !isPrivateOrFilePrivate(node.modifiers) else { return .skipChildren } recordSymbol(name: node.name.text, kind: ClassificationKeywords.enumWord, node: node) return .skipChildren } override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { + guard !isPrivateOrFilePrivate(node.modifiers) else { return .skipChildren } recordSymbol(name: node.name.text, kind: ClassificationKeywords.protocolWord, node: node) return .skipChildren } override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { + guard !isPrivateOrFilePrivate(node.modifiers) else { return .skipChildren } recordSymbol(name: node.name.text, kind: ClassificationKeywords.actorWord, node: node) return .skipChildren } override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind { + guard !isPrivateOrFilePrivate(node.modifiers) else { return .skipChildren } recordSymbol(name: node.name.text, kind: ClassificationKeywords.funcWord, node: node) return .skipChildren } override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind { + guard !isPrivateOrFilePrivate(node.modifiers) else { return .skipChildren } guard let binding = node.bindings.first, let pattern = binding.pattern.as(IdentifierPatternSyntax.self), let keyword: ClassificationKeywords = ClassificationKeywords(rawValue: node.bindingSpecifier.text) else { @@ -58,11 +60,14 @@ class SwiftDeclarationCollector: SyntaxVisitor { override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { let name = node.extendedType.trimmedDescription + guard !isPrivateOrFilePrivate(node.modifiers) && name != "View" else { return .skipChildren } + recordSymbol(name: name, kind: ClassificationKeywords.extensionWord, node: node) return .skipChildren } override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { + guard !isPrivateOrFilePrivate(node.modifiers) else { return .skipChildren } recordSymbol(name: node.name.text, kind: ClassificationKeywords.typealiasWord, node: node) return .skipChildren } @@ -87,4 +92,12 @@ class SwiftDeclarationCollector: SyntaxVisitor { ) symbols.append(symbol) } + + private func isPrivateOrFilePrivate(_ modifiers: DeclModifierListSyntax?) -> Bool { + guard let modifiers else { return false } + return modifiers.contains { modifier in + let text = modifier.name.text + return text == "private" || text == "fileprivate" + } + } }