Generator/Sources/NeedleFramework/Generating/Serializers/OutputSerializer.swift (101 lines of code) (raw):

// // Copyright (c) 2018. Uber Technologies // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // import Foundation /// A utility class that serializes a set of providers and necessary /// import statements into the final output file content. class OutputSerializer: Serializer { /// Initializer. /// /// - parameter providers: The list of providers to output. /// - parameter imports: The list of import statements to include. /// - parameter headerDocContent: The content of the header doc to /// include at the top of the output file. init(providers: [SerializedProvider], dynamicProviders: [SerializedProvider], imports: [String], headerDocContent: String, needleVersionHash: String? = nil) { self.providers = providers self.dynamicProviders = dynamicProviders self.imports = imports self.headerDocContent = headerDocContent self.needleVersionHash = needleVersionHash } /// Serialize the data model into source code. /// /// - returns: The source code `String`. func serialize() -> String { // Dependency factories have deterministic names, so we can only define a // factory once, or the code won't compile. var registeredFactories: Set<String> = [] // We generate parent traversal functions to improve compile time on large // codebases. This tells us how many levels deep the tree goes. var maxLevel: Int = 1 let providersSection = providers .map { (provider: SerializedProvider) in if let providerMaxLevel = provider.attributes.maxLevel { if providerMaxLevel > maxLevel { maxLevel = providerMaxLevel } } if let factoryName = provider.attributes.factoryName { if registeredFactories.contains(factoryName) { return "" } registeredFactories.insert(factoryName) } return provider.content } .joined() let dynamicProvidersSection = dynamicProviders .map { (provider: SerializedProvider) in provider.content } .joined() let traversalHelpers = (1...maxLevel).map { num in return """ private func parent\(num)(_ component: NeedleFoundation.Scope) -> NeedleFoundation.Scope { return component\(String(repeating: ".parent", count: num)) } """ }.joined(separator: "\n\n") let needleDependenciesHash = needleVersionHash.map { return "\"\($0)\""} ?? "nil" let importsJoined = imports.joined(separator: "\n") // With Swift 5.6 and an amd64 target, having a function body that is // thousands of lines long causes a severe compile performance // regression. We were seeing compilation take 4.5x what an x86_64 // target would take. As a result, this code splits the calls to // register the dependencies into functions around 50 lines long. // Through some basic testing, this seemed to produce the best results. let linesPerHelper = 50 var registrationHelperFuncs: [String] = [] let registrations: [String] = providers .map { (provider: SerializedProvider) in provider.registration } .filter { !$0.isEmpty } let registrationBody = stride(from: 0, to: registrations.count, by: linesPerHelper) .map { let helperBody = registrations[$0 ..< Swift.min($0 + linesPerHelper, registrations.count)] .joined() .replacingOccurrences(of: "\n", with: "\n ") .trimmingCharacters(in: .whitespacesAndNewlines) let regNum = ($0 / linesPerHelper) + 1 registrationHelperFuncs.append(""" @inline(never) private func register\(regNum)() { \(helperBody) } """) return "register\(regNum)()" } .joined(separator: "\n ") let registrationHelpers = registrationHelperFuncs.joined(separator: "\n\n") return """ \(headerDocContent) \(importsJoined) // swiftlint:disable unused_declaration private let needleDependenciesHash : String? = \(needleDependenciesHash) // MARK: - Traversal Helpers \(traversalHelpers) // MARK: - Providers #if !NEEDLE_DYNAMIC \(providersSection) #else \(dynamicProvidersSection) #endif private func factoryEmptyDependencyProvider(_ component: NeedleFoundation.Scope) -> AnyObject { return EmptyDependencyProvider(component: component) } // MARK: - Registration private func registerProviderFactory(_ componentPath: String, _ factory: @escaping (NeedleFoundation.Scope) -> AnyObject) { __DependencyProviderRegistry.instance.registerDependencyProviderFactory(for: componentPath, factory) } #if !NEEDLE_DYNAMIC \(registrationHelpers) #endif public func registerProviderFactories() { #if !NEEDLE_DYNAMIC \(registrationBody) #endif } """ } // MARK: - Private private let providers: [SerializedProvider] private let dynamicProviders: [SerializedProvider] private let imports: [String] private let headerDocContent: String private let needleVersionHash: String? }