Skip to content

Commit

Permalink
Merge branch 'fix-macro-init'
Browse files Browse the repository at this point in the history
  • Loading branch information
elsh committed Apr 9, 2020
2 parents d60fb32 + 2b678de commit e53edab
Show file tree
Hide file tree
Showing 21 changed files with 595 additions and 172 deletions.
1 change: 1 addition & 0 deletions Sources/MockoloFramework/Models/ParsedEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ struct AnnotationMetadata {
var varTypes: [String: String]?
}

public typealias ImportMap = [String: [String: [String]]]

/// Metadata for a type being mocked
public final class Entity {
Expand Down
46 changes: 20 additions & 26 deletions Sources/MockoloFramework/Operations/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,25 @@ public func generate(sourceDirs: [String]?,
scanConcurrencyLimit = concurrencyLimit
minLogLevel = loggingLevel
var candidates = [(String, Int64)]()
var resolvedEntities = [ResolvedEntity]()
var parentMocks = [String: Entity]()
var annotatedProtocolMap = [String: Entity]()
var protocolMap = [String: Entity]()
var annotatedProtocolMap = [String: Entity]()
var pathToContentMap = [(String, Data, Int64)]()
var pathToImportsMap = [String: [String]]()
var relevantPaths = [String]()
var resolvedEntities = [ResolvedEntity]()
var pathToImportsMap = ImportMap()

signpost_begin(name: "Process input")
let t0 = CFAbsoluteTimeGetCurrent()
log("Process input mock files...", level: .info)
if let mockFilePaths = mockFilePaths, !mockFilePaths.isEmpty {
parser.parseProcessedDecls(mockFilePaths) { (elements, imports) in
parser.parseProcessedDecls(mockFilePaths, fileMacro: macro) { (elements, imports) in
elements.forEach { element in
parentMocks[element.entityNode.name] = element
}

if let imports = imports {
for (path, modules) in imports {
if pathToImportsMap[path] == nil {
pathToImportsMap[path] = []
}
pathToImportsMap[path]?.append(contentsOf: modules)
for (path, importMap) in imports {
pathToImportsMap[path] = importMap
}
}
}
Expand All @@ -85,13 +81,13 @@ public func generate(sourceDirs: [String]?,

signpost_begin(name: "Generate protocol map")
log("Process source files and generate an annotated/protocol map...", level: .info)

let paths = sourceDirs ?? sourceFiles
let isDirs = sourceDirs != nil
parser.parseDecls(paths,
isDirs: isDirs,
exclusionSuffixes: exclusionSuffixes,
annotation: annotation,
fileMacro: macro,
declType: declType) { (elements, imports) in
elements.forEach { element in
protocolMap[element.entityNode.name] = element
Expand All @@ -100,33 +96,29 @@ public func generate(sourceDirs: [String]?,
}
}
if let imports = imports {
for (path, modules) in imports {
if pathToImportsMap[path] == nil {
pathToImportsMap[path] = []
}
pathToImportsMap[path]?.append(contentsOf: modules)
for (path, importMap) in imports {
pathToImportsMap[path] = importMap
}
}
}
signpost_end(name: "Generate protocol map")
let t2 = CFAbsoluteTimeGetCurrent()
log("Took", t2-t1, level: .verbose)

signpost_begin(name: "Generate models")
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)
relevantPaths.append(contentsOf: container.paths)
resolvedEntities.append(container.entity)
})
signpost_end(name: "Generate models")
Expand All @@ -142,17 +134,19 @@ public func generate(sourceDirs: [String]?,
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,
testableImports: testableImports)

let result = write(candidates: candidates,
pathToImportsMap: pathToImportsMap,
relevantPaths: relevantPaths,
pathToContentMap: pathToContentMap,
header: header,
macro: macro,
testableImports: testableImports,
customImports: customImports,
imports: imports,
to: outputFilePath)
signpost_end(name: "Write results")
let t5 = CFAbsoluteTimeGetCurrent()
Expand Down
71 changes: 71 additions & 0 deletions Sources/MockoloFramework/Operations/ImportsHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// ImportsHandler.swift
// MockoloFramework
//
// Created by Ellie on 4/8/20.
//

import Foundation

func handleImports(pathToImportsMap: ImportMap,
pathToContentMap: [(String, Data, Int64)], // TODO: is this needed??
customImports: [String]?,
testableImports: [String]?) -> String {

var importLines = [String: [String]]()
let defaultKey = ""
if importLines[defaultKey] == nil {
importLines[defaultKey] = []
}

for (_, importMap) in pathToImportsMap {
for (k, v) in importMap {
if importLines[k] == nil {
importLines[k] = []
}
importLines[k]?.append(contentsOf: v)
}
}

for (_, filecontent, offset) in pathToContentMap {
let v = filecontent.findImportLines(at: offset)
importLines[defaultKey]?.append(contentsOf: v)
// break // TODO: why break here?
}

if let customImports = customImports {
importLines[defaultKey]?.append(contentsOf: customImports.map {$0.asImport})
}

var sortedImports = [String: [String]]()
for (k, v) in importLines {
sortedImports[k] = Set(v).sorted()
}

if let existingSet = sortedImports[defaultKey] {
if let testableImports = testableImports {
let nonTestableInList = existingSet.filter { !testableImports.contains($0.moduleNameInImport) }.map{$0}
let testableInList = existingSet.filter { testableImports.contains($0.moduleNameInImport) }.map{ "@testable " + $0 }
let remainingTestable = testableImports.filter { !testableInList.contains($0) }.map {$0.asTestableImport}
let testable = Set([testableInList, remainingTestable].flatMap{$0}).sorted()
sortedImports[defaultKey] = [nonTestableInList, testable].flatMap{$0}
}
}

let sortedKeys = sortedImports.keys.sorted()
let importsStr = sortedKeys.map { k in
let v = sortedImports[k]
let lines = v?.joined(separator: "\n") ?? ""
if k.isEmpty {
return lines
} else {
return """
#if \(k)
\(lines)
#endif
"""
}
}.joined(separator: "\n")

return importsStr
}
45 changes: 2 additions & 43 deletions Sources/MockoloFramework/Operations/OutputWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,51 +18,10 @@ import Foundation

/// Combines a list of entities and import lines and header and writes the final output
func write(candidates: [(String, Int64)],
pathToImportsMap: [String: [String]],
relevantPaths: [String],
pathToContentMap: [(String, Data, Int64)],
header: String?,
macro: String?,
testableImports: [String]?,
customImports: [String]?,
imports: String,
to outputFilePath: String) -> String {

var importLines = [String]()
for path in relevantPaths {
if let lines = pathToImportsMap[path] {
importLines.append(contentsOf: lines)
}
}
for (_, filecontent, offset) in pathToContentMap {
let v = findImportLines(data: filecontent, offset: offset)
importLines.append(contentsOf: v)
break
}

if let customImports = customImports {
importLines.append(contentsOf: customImports.map {$0.asImport})
}

var importLineStr = ""

if let testableImports = testableImports {
var imports = importLines.compactMap { (importLine) -> String? in
return importLine.moduleName
}
imports.append(contentsOf: testableImports)
importLineStr = Set(imports)
.sorted()
.map { testableModuleName -> String in
guard testableImports.contains(testableModuleName) else {
return testableModuleName.asImport
}
return testableModuleName.asTestableImport
}
.joined(separator: "\n")
} else {
let importsSet = Set(importLines.map{$0.trimmingCharacters(in: .whitespaces)})
importLineStr = importsSet.sorted().joined(separator: "\n")
}

let entities = candidates
.sorted { (left: (String, Int64), right: (String, Int64)) -> Bool in
Expand All @@ -80,7 +39,7 @@ func write(candidates: [(String, Int64)],
macroStart = .poundIf + mcr
macroEnd = .poundEndIf
}
let ret = [headerStr, macroStart, importLineStr, entities.joined(separator: "\n"), macroEnd].joined(separator: "\n\n")
let ret = [headerStr, macroStart, imports, entities.joined(separator: "\n"), macroEnd].joined(separator: "\n\n")

_ = try? ret.write(toFile: outputFilePath, atomically: true, encoding: .utf8)
return ret
Expand Down
49 changes: 27 additions & 22 deletions Sources/MockoloFramework/Parsers/ParserViaSourceKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ import Foundation
import SourceKittenFramework

public class ParserViaSourceKit: SourceParsing {

public init() {}

public func parseProcessedDecls(_ paths: [String],
completion: @escaping ([Entity], [String: [String]]?) -> ()) {
scan(paths) { (filePath, lock) in
fileMacro: String?,
completion: @escaping ([Entity], ImportMap?) -> ()) {
scan(paths) { (filePath, lock) in
self.generateProcessedASTs(filePath, lock: lock, completion: completion)
}
}
Expand All @@ -31,27 +33,29 @@ public class ParserViaSourceKit: SourceParsing {
isDirs: Bool,
exclusionSuffixes: [String]? = nil,
annotation: String,
fileMacro: String?,
declType: DeclType,
completion: @escaping ([Entity], [String: [String]]?) -> ()) {
completion: @escaping ([Entity], ImportMap?) -> ()) {
guard !annotation.isEmpty else { return }
guard let paths = paths else { return }
if isDirs {
generateASTs(dirs: paths, exclusionSuffixes: exclusionSuffixes, annotation: annotation, declType: declType, completion: completion)
generateASTs(dirs: paths, exclusionSuffixes: exclusionSuffixes, annotation: annotation, fileMacro: fileMacro, declType: declType, completion: completion)
} else {
generateASTs(files: paths, exclusionSuffixes: exclusionSuffixes, annotation: annotation, declType: declType, completion: completion)
generateASTs(files: paths, exclusionSuffixes: exclusionSuffixes, annotation: annotation, fileMacro: fileMacro, declType: declType, completion: completion)
}
}

private func generateASTs(dirs: [String],
exclusionSuffixes: [String]? = nil,
annotation: String,
declType: DeclType,
completion: @escaping ([Entity], [String: [String]]?) -> ()) {
exclusionSuffixes: [String]? = nil,
annotation: String,
fileMacro: String?,
declType: DeclType,
completion: @escaping ([Entity], ImportMap?) -> ()) {

guard let annotationData = annotation.data(using: .utf8) else {
fatalError("Annotation is invalid: \(annotation)")
}

scan(dirs: dirs) { (path, lock) in
self.generateASTs(path,
exclusionSuffixes: exclusionSuffixes,
Expand All @@ -65,12 +69,13 @@ public class ParserViaSourceKit: SourceParsing {
private func generateASTs(files: [String],
exclusionSuffixes: [String]? = nil,
annotation: String,
fileMacro: String?,
declType: DeclType,
completion: @escaping ([Entity], [String: [String]]?) -> ()) {
completion: @escaping ([Entity], ImportMap?) -> ()) {
guard let annotationData = annotation.data(using: .utf8) else {
fatalError("Annotation is invalid: \(annotation)")
}

scan(files) { (path, lock) in
self.generateASTs(path,
exclusionSuffixes: exclusionSuffixes,
Expand All @@ -82,11 +87,11 @@ public class ParserViaSourceKit: SourceParsing {
}

private func generateASTs(_ path: String,
exclusionSuffixes: [String]? = nil,
annotationData: Data,
declType: DeclType,
lock: NSLock?,
completion: @escaping ([Entity], [String: [String]]?) -> ()) {
exclusionSuffixes: [String]? = nil,
annotationData: Data,
declType: DeclType,
lock: NSLock?,
completion: @escaping ([Entity], ImportMap?) -> ()) {

guard path.shouldParse(with: exclusionSuffixes) else { return }
guard let content = FileManager.default.contents(atPath: path) else {
Expand All @@ -108,7 +113,7 @@ public class ParserViaSourceKit: SourceParsing {
case .all:
parseCurrent = true
}

guard parseCurrent else { continue }
let metadata = current.annotationMetadata(with: annotationData, in: content)
if let node = Entity.node(with: current, filepath: path, data: content, isPrivate: current.isPrivate, isFinal: current.isFinal, metadata: metadata, processed: false) {
Expand All @@ -126,8 +131,8 @@ public class ParserViaSourceKit: SourceParsing {
}

private func generateProcessedASTs(_ path: String,
lock: NSLock?,
completion: @escaping ([Entity], [String: [String]]) -> ()) {
lock: NSLock?,
completion: @escaping ([Entity], ImportMap?) -> ()) {

guard let content = FileManager.default.contents(atPath: path) else {
fatalError("Retrieving contents of \(path) failed")
Expand All @@ -140,9 +145,9 @@ public class ParserViaSourceKit: SourceParsing {
return Entity.node(with: current, filepath: path, data: content, isPrivate: current.isPrivate, isFinal: current.isFinal, metadata: nil, processed: true)
}

let imports = findImportLines(data: content, offset: subs.first?.offset)
let imports = content.findImportLines(at: subs.first?.offset)
lock?.lock()
completion(results, [path: imports])
completion(results, [path: ["": imports]])
lock?.unlock()
} catch {
fatalError(error.localizedDescription)
Expand Down
Loading

0 comments on commit e53edab

Please sign in to comment.