diff options
Diffstat (limited to 'SoraTests/XCTestCase+SourceAnalysis.swift')
| -rw-r--r-- | SoraTests/XCTestCase+SourceAnalysis.swift | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/SoraTests/XCTestCase+SourceAnalysis.swift b/SoraTests/XCTestCase+SourceAnalysis.swift new file mode 100644 index 0000000..4cd907f --- /dev/null +++ b/SoraTests/XCTestCase+SourceAnalysis.swift @@ -0,0 +1,210 @@ +import Foundation +import XCTest + +extension XCTestCase { + func loadSource(at relativePath: String) throws -> String { + let currentFile = URL(fileURLWithPath: #filePath) + let repositoryRoot = currentFile.deletingLastPathComponent().deletingLastPathComponent() + let fileURL = repositoryRoot.appendingPathComponent(relativePath) + + return try String(contentsOf: fileURL, encoding: .utf8) + } + + // swiftlint:disable:next cyclomatic_complexity + func extractFunction(named signature: String, from source: String) throws -> String { + guard let signatureRange = source.range(of: signature) else { + throw NSError(domain: "SourceAnalysisTests", code: 1) + } + guard let openingBrace = source[signatureRange.upperBound...].firstIndex(of: "{") else { + throw NSError(domain: "SourceAnalysisTests", code: 2) + } + + let characters = Array(source) + let startOffset = source.distance(from: source.startIndex, to: signatureRange.lowerBound) + var currentOffset = source.distance(from: source.startIndex, to: openingBrace) + + var braceDepth = 0 + var inLineComment = false + var blockCommentDepth = 0 + var inString = false + var isEscaped = false + + while currentOffset < characters.count { + let current = characters[currentOffset] + let next: Character? = currentOffset + 1 < characters.count ? characters[currentOffset + 1] : nil + + if inLineComment { + if current == "\n" { + inLineComment = false + } + currentOffset += 1 + continue + } + + if blockCommentDepth > 0 { + if current == "/", next == "*" { + blockCommentDepth += 1 + currentOffset += 2 + continue + } + + if current == "*", next == "/" { + blockCommentDepth -= 1 + currentOffset += 2 + continue + } + + currentOffset += 1 + continue + } + + if inString { + if isEscaped { + isEscaped = false + } else if current == "\\" { + isEscaped = true + } else if current == "\"" { + inString = false + } + + currentOffset += 1 + continue + } + + if current == "/", next == "/" { + inLineComment = true + currentOffset += 2 + continue + } + + if current == "/", next == "*" { + blockCommentDepth = 1 + currentOffset += 2 + continue + } + + if current == "\"" { + inString = true + currentOffset += 1 + continue + } + + if current == "{" { + braceDepth += 1 + } else if current == "}" { + braceDepth -= 1 + + if braceDepth == 0 { + let endIndex = source.index(source.startIndex, offsetBy: currentOffset + 1) + let startIndex = source.index(source.startIndex, offsetBy: startOffset) + + return String(source[startIndex..<endIndex]) + } + } + + currentOffset += 1 + } + + throw NSError(domain: "SourceAnalysisTests", code: 3) + } + + // swiftlint:disable:next cyclomatic_complexity + func strippingCommentsAndStrings(from source: String) -> String { + let characters = Array(source) + var result: [Character] = [] + result.reserveCapacity(characters.count) + + var currentOffset = 0 + var inLineComment = false + var blockCommentDepth = 0 + var inString = false + var isEscaped = false + + while currentOffset < characters.count { + let current = characters[currentOffset] + let next: Character? = currentOffset + 1 < characters.count ? characters[currentOffset + 1] : nil + + if inLineComment { + if current == "\n" { + inLineComment = false + result.append("\n") + } else { + result.append(" ") + } + currentOffset += 1 + continue + } + + if blockCommentDepth > 0 { + if current == "/", next == "*" { + blockCommentDepth += 1 + result.append(" ") + result.append(" ") + currentOffset += 2 + continue + } + + if current == "*", next == "/" { + blockCommentDepth -= 1 + result.append(" ") + result.append(" ") + currentOffset += 2 + continue + } + + result.append(current == "\n" ? "\n" : " ") + currentOffset += 1 + continue + } + + if inString { + if isEscaped { + isEscaped = false + } else if current == "\\" { + isEscaped = true + } else if current == "\"" { + inString = false + } + + result.append(current == "\n" ? "\n" : " ") + currentOffset += 1 + continue + } + + if current == "/", next == "/" { + inLineComment = true + result.append(" ") + result.append(" ") + currentOffset += 2 + continue + } + + if current == "/", next == "*" { + blockCommentDepth = 1 + result.append(" ") + result.append(" ") + currentOffset += 2 + continue + } + + if current == "\"" { + inString = true + result.append(" ") + currentOffset += 1 + continue + } + + result.append(current) + currentOffset += 1 + } + + return String(result) + } + + func tokenCount(matching pattern: String, in source: String) -> Int { + guard let regex = try? NSRegularExpression(pattern: pattern) else { return 0 } + let range = NSRange(source.startIndex..<source.endIndex, in: source) + + return regex.numberOfMatches(in: source, range: range) + } +} |