Sources/MockoloFramework/Operations/Generator.swift (140 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 CoreFoundation import Foundation enum InputError: Error { case annotationError case sourceFilesError } /// Performs end to end mock generation flow public func generate(sourceDirs: [String], sourceFiles: [String], parser: SourceParser, exclusionSuffixes: [String], mockFilePaths: [String]?, annotation: String, header: String?, macro: String?, declType: DeclType, useTemplateFunc: Bool, useMockObservable: Bool, allowSetCallCount: Bool, enableFuncArgsHistory: Bool, disableCombineDefaultValues: Bool, mockFinal: Bool, testableImports: [String], customImports: [String], excludeImports: [String], to outputFilePath: String, loggingLevel: Int, concurrencyLimit: Int?, onCompletion: @escaping (String) -> ()) throws { guard sourceDirs.count > 0 || sourceFiles.count > 0 else { log("Source files or directories do not exist", level: .error) throw InputError.sourceFilesError } scanConcurrencyLimit = concurrencyLimit minLogLevel = loggingLevel var candidates = [(String, Int64)]() var resolvedEntities = [ResolvedEntity]() var parentMocks = [String: Entity]() var protocolMap = [String: Entity]() var annotatedProtocolMap = [String: Entity]() var pathToContentMap = [(String, Data, Int64)]() var pathToImportsMap = ImportMap() var relevantPaths = [String]() signpost_begin(name: "Process input") let t0 = CFAbsoluteTimeGetCurrent() log("Process input mock files...", level: .info) if let mockFilePaths = mockFilePaths, !mockFilePaths.isEmpty { parser.parseProcessedDecls(mockFilePaths, fileMacro: macro) { (elements, imports) in elements.forEach { element in parentMocks[element.entityNode.nameText] = element } if let imports = imports { for (path, importMap) in imports { pathToImportsMap[path] = importMap } } } } signpost_end(name: "Process input") let t1 = CFAbsoluteTimeGetCurrent() log("Took", t1-t0, level: .verbose) signpost_begin(name: "Generate protocol map") log("Process source files and generate an annotated/protocol map...", level: .info) let paths = !sourceDirs.isEmpty ? sourceDirs : sourceFiles let isDirs = !sourceDirs.isEmpty parser.parseDecls(paths, isDirs: isDirs, exclusionSuffixes: exclusionSuffixes, annotation: annotation, fileMacro: macro, declType: declType) { (elements, imports) in elements.forEach { element in protocolMap[element.entityNode.nameText] = element if element.isAnnotated { annotatedProtocolMap[element.entityNode.nameText] = element } } if let imports = imports { for (path, importMap) in imports { pathToImportsMap[path] = importMap } } } signpost_end(name: "Generate protocol map") let t2 = CFAbsoluteTimeGetCurrent() log("Took", t2-t1, level: .verbose) let typeKeyList = [parentMocks.compactMap {$0.key.components(separatedBy: "Mock").first}, annotatedProtocolMap.map {$0.key}].flatMap{$0} var typeKeys = [String: String]() typeKeyList.forEach { (t: String) in typeKeys[t] = "\(t)Mock()" } Type.customTypeMap = typeKeys signpost_begin(name: "Generate models") log("Resolve inheritance and generate unique entity models...", level: .info) generateUniqueModels(protocolMap: protocolMap, annotatedProtocolMap: annotatedProtocolMap, inheritanceMap: parentMocks, completion: { container in pathToContentMap.append(contentsOf: container.imports) resolvedEntities.append(container.entity) relevantPaths.append(contentsOf: container.paths) }) signpost_end(name: "Generate models") let t3 = CFAbsoluteTimeGetCurrent() log("Took", t3-t2, level: .verbose) signpost_begin(name: "Render models") log("Render models with templates...", level: .info) renderTemplates(entities: resolvedEntities, useTemplateFunc: useTemplateFunc, useMockObservable: useMockObservable, allowSetCallCount: allowSetCallCount, mockFinal: mockFinal, enableFuncArgsHistory: enableFuncArgsHistory, disableCombineDefaultValues: disableCombineDefaultValues ) { (mockString: String, offset: Int64) in candidates.append((mockString, offset)) } signpost_end(name: "Render models") let t4 = CFAbsoluteTimeGetCurrent() log("Took", t4-t3, level: .verbose) signpost_begin(name: "Write results") log("Write the mock results and import lines to", outputFilePath, level: .info) let imports = handleImports(pathToImportsMap: pathToImportsMap, pathToContentMap: pathToContentMap, customImports: customImports, excludeImports: excludeImports, testableImports: testableImports, relevantPaths: relevantPaths) let result = try write(candidates: candidates, header: header, macro: macro, imports: imports, to: outputFilePath) signpost_end(name: "Write results") let t5 = CFAbsoluteTimeGetCurrent() log("Took", t5-t4, level: .verbose) let count = result.components(separatedBy: "\n").count log("TOTAL", t5-t0, level: .verbose) log("#Protocols = \(protocolMap.count), #Annotated protocols = \(annotatedProtocolMap.count), #Parent mock classes = \(parentMocks.count), #Final mock classes = \(candidates.count), File LoC = \(count)", level: .verbose) onCompletion(result) }