normalize-public-api.swift (3022B)
1 import Foundation 2 3 private func fail(_ message: String) -> Never { 4 FileHandle.standardError.write(Data("api_baseline_invalid: \(message)\n".utf8)) 5 exit(1) 6 } 7 8 private func string(_ object: [String: Any], _ key: String, _ context: String) -> String { 9 guard let value = object[key] as? String else { 10 fail("\(context) omits \(key)") 11 } 12 return value 13 } 14 15 private func escaped(_ value: String) -> String { 16 value 17 .replacingOccurrences(of: "\\", with: "\\\\") 18 .replacingOccurrences(of: "\t", with: "\\t") 19 .replacingOccurrences(of: "\n", with: "\\n") 20 .replacingOccurrences(of: "\r", with: "\\r") 21 } 22 23 private func fields(_ values: [String]) -> String { 24 values 25 .map { "\($0.utf8.count):\(escaped($0))" } 26 .joined(separator: "\t") 27 } 28 29 let paths = Array(CommandLine.arguments.dropFirst()) 30 guard !paths.isEmpty else { 31 fail("at least one symbol graph is required") 32 } 33 34 var modules = Set<String>() 35 var rows = Set<String>() 36 37 for path in paths.sorted() { 38 let data: Data 39 do { 40 data = try Data(contentsOf: URL(fileURLWithPath: path)) 41 } catch { 42 fail("symbol graph could not be read") 43 } 44 guard 45 let root = try? JSONSerialization.jsonObject(with: data) as? [String: Any], 46 let module = root["module"] as? [String: Any], 47 let symbols = root["symbols"] as? [[String: Any]], 48 let relationships = root["relationships"] as? [[String: Any]] 49 else { 50 fail("symbol graph shape is invalid") 51 } 52 53 let moduleName = string(module, "name", "module") 54 modules.insert(moduleName) 55 56 func record(_ row: String) { 57 rows.insert(row) 58 } 59 60 for symbol in symbols { 61 guard 62 let kind = symbol["kind"] as? [String: Any], 63 let identifier = symbol["identifier"] as? [String: Any], 64 let names = symbol["names"] as? [String: Any] 65 else { 66 fail("symbol shape is invalid") 67 } 68 let declaration = (symbol["declarationFragments"] as? [[String: Any]] ?? []) 69 .map { string($0, "spelling", "declaration fragment") } 70 .joined() 71 record(fields([ 72 "symbol", 73 moduleName, 74 string(kind, "identifier", "symbol kind"), 75 string(identifier, "precise", "symbol identifier"), 76 string(names, "title", "symbol names"), 77 declaration, 78 ])) 79 } 80 81 for relationship in relationships { 82 record(fields([ 83 "relationship", 84 moduleName, 85 string(relationship, "kind", "relationship"), 86 string(relationship, "source", "relationship"), 87 string(relationship, "target", "relationship"), 88 relationship["targetFallback"] as? String ?? "", 89 ])) 90 } 91 } 92 93 guard modules == ["RadrootsKit", "RadrootsKitTesting"] else { 94 fail("module inventory is not exact") 95 } 96 97 print("radroots.apple-kit.public-api.v1") 98 let sortedRows = rows.sorted() 99 for row in sortedRows { 100 print(row) 101 }