Cuckoo/Generator/Source/CuckooGeneratorFramework/Generator.swift

126 lines
5.0 KiB
Swift

//
// Generator.swift
// CuckooGenerator
//
// Created by Tadeas Kriz on 13/01/16.
// Copyright © 2016 Brightify. All rights reserved.
//
import Foundation
import Stencil
public struct Generator {
private let declarations: [Token]
private let code = CodeBuilder()
public init(file: FileRepresentation) {
declarations = file.declarations
}
public func generate(debug: Bool = false) throws -> String {
code.clear()
let ext = Extension()
ext.registerFilter("genericSafe") { (value: Any?) in
guard let string = value as? String else { return value }
return self.genericSafeType(from: string)
}
ext.registerFilter("matchableGenericNames") { (value: Any?) in
guard let parameters = value as? [MethodParameter] else { return value }
return self.matchableGenerics(with: parameters)
}
ext.registerFilter("matchableGenericWhere") { (value: Any?) in
guard let parameters = value as? [MethodParameter] else { return value }
return self.matchableGenerics(where: parameters)
}
ext.registerFilter("matchableParameterSignature") { (value: Any?) in
guard let parameters = value as? [MethodParameter] else { return value }
return self.matchableParameterSignature(with: parameters)
}
ext.registerFilter("parameterMatchers") { (value: Any?) in
guard let parameters = value as? [MethodParameter] else { return value }
return self.parameterMatchers(for: parameters)
}
ext.registerFilter("openNestedClosure") { (value: Any?, arguments: [Any?]) in
guard let parameters = value as? [MethodParameter] else { return value }
let s = self.openNestedClosure(for: parameters, throwing: arguments.first as? Bool)
return s
}
ext.registerFilter("closeNestedClosure") { (value: Any?) in
guard let parameters = value as? [MethodParameter] else { return value }
return self.closeNestedClosure(for: parameters)
}
let environment = Environment(extensions: [ext])
let containers = declarations.compactMap { $0 as? ContainerToken }
.filter { $0.accessibility.isAccessible }
.map { $0.serializeWithType() }
return try environment.renderTemplate(string: Templates.mock, context: ["containers": containers, "debug": debug])
}
private func matchableGenerics(with parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "" }
let genericParameters = (1...parameters.count).map { "M\($0): Cuckoo.Matchable" }.joined(separator: ", ")
return "<\(genericParameters)>"
}
private func matchableGenerics(where parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "" }
let whereClause = parameters.enumerated().map { "M\($0 + 1).MatchedType == \(genericSafeType(from: $1.typeWithoutAttributes))" }.joined(separator: ", ")
return " where \(whereClause)"
}
private func matchableParameterSignature(with parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "" }
return parameters.enumerated().map { "\($1.labelAndName): M\($0 + 1)" }.joined(separator: ", ")
}
private func parameterMatchers(for parameters: [MethodParameter]) -> String {
guard parameters.isEmpty == false else { return "let matchers: [Cuckoo.ParameterMatcher<Void>] = []" }
let tupleType = parameters.map { $0.typeWithoutAttributes }.joined(separator: ", ")
let matchers = parameters.enumerated().map { "wrap(matchable: \($1.name)) { $0\(parameters.count > 1 ? ".\($0)" : "") }" }.joined(separator: ", ")
return "let matchers: [Cuckoo.ParameterMatcher<(\(genericSafeType(from: tupleType)))>] = [\(matchers)]"
}
private func genericSafeType(from type: String) -> String {
return type.replacingOccurrences(of: "!", with: "?")
}
private func openNestedClosure(for parameters: [MethodParameter], throwing: Bool? = false) -> String {
var fullString = ""
for (index, parameter) in parameters.enumerated() {
if parameter.isClosure && !parameter.isEscaping {
let indents = String(repeating: "\t", count: index + 1)
let tries = (throwing ?? false) ? " try " : " "
fullString += "\(indents)return\(tries)withoutActuallyEscaping(\(parameter.name), do: { (\(parameter.name)) in\n"
}
}
return fullString
}
private func closeNestedClosure(for parameters: [MethodParameter]) -> String {
var fullString = ""
for (index, parameter) in parameters.enumerated() {
if parameter.isClosure && !parameter.isEscaping {
let indents = String(repeating: "\t", count: index + 1)
fullString += "\(indents)})\n"
}
}
return fullString
}
}