exposes a protocol - very similar to decodable. • Many features implement types which conform to it. New package decodes these types from data. • A fallback json fi le is shipped with the app. It must always work with all models. • Decoding errors are all runtime and would cause issues when certain screens are accessed. • We gained con fi dence by testing, but doing this manually is not very scalable… • Very di ff i cult to automate at the time as we have a mix of Swift Packages and Xcode projects. • Xcode 14 will allow us to use plugins for Xcode projects too! 🎉
Swift Package Plugin. • Uses SourceKitten (SourceKit under the hood) to fi nd all types conforming to a speci fi c protocol. • Automatically generates tests for each type which conforms to such protocol.
PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String } Package.swift 📂 Sources 📂 CodeGenSample CodeGenSample.swift 📂 PluginExecutable PluginExecutable.swift 📂 Tests 📂 CodeGenSampleTests Empty.swift
PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { } } Package.swift 📂 Sources 📂 CodeGenSample CodeGenSample.swift 📂 PluginExecutable PluginExecutable.swift 📂 Tests 📂 CodeGenSampleTests Empty.swift
PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) } } Package.swift 📂 Sources 📂 CodeGenSample CodeGenSample.swift 📂 PluginExecutable PluginExecutable.swift 📂 Tests 📂 CodeGenSampleTests Empty.swift
PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) let structures = try files.map { try Structure(file: File(path: $0.path)!) } } } Package.swift 📂 Sources 📂 CodeGenSample CodeGenSample.swift 📂 PluginExecutable PluginExecutable.swift 📂 Tests 📂 CodeGenSampleTests Empty.swift
PluginExecutable: ParsableCommand { @Argument(help: "The protocol name to match") var protocolName: String @Argument(help: "The module's name") var moduleName: String @Option(help: "Directory containing the swift files") var input: String @Option(help: "The path where the generated files will be created") var output: String func run() throws { // Needed to ensure that sourcekit runs in a single process setenv("IN_PROCESS_SOURCEKIT", "YES", 1) let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true)) let structures = try files.map { try Structure(file: File(path: $0.path)!) } var matchedTypes = [String]() structures.forEach { walkTree(dictionary: $0.dictionary, acc: &matchedTypes) } try createOutputFile(withContent: matchedTypes) } } Package.swift 📂 Sources 📂 CodeGenSample CodeGenSample.swift 📂 PluginExecutable PluginExecutable.swift 📂 Tests 📂 CodeGenSampleTests Empty.swift