Skip to content

Commit b384a7d

Browse files
authored
Fix Name Override With Inheritance (#338)
* Handle name overrides and inheritance properly. * Test we handle inheritance properly. * Rename `classesHaveBeenProcessed` -> `scanAsMockfile`. * Use static fixtures, not functions and loops.
1 parent 31c307c commit b384a7d

7 files changed

Lines changed: 416 additions & 22 deletions

File tree

Sources/MockoloFramework/Operations/Generator.swift

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public func generate(sourceDirs: [String],
5353
var candidates = [(String, Int64)]()
5454
var resolvedEntities = [ResolvedEntity]()
5555
var parentMocks = [String: Entity]()
56+
var parentMocksByInheritedType = [String: Entity]()
5657
var protocolMap = [String: Entity]()
5758
var annotatedProtocolMap = [String: Entity]()
5859
var pathToImportsMap = ImportMap()
@@ -74,6 +75,12 @@ public func generate(sourceDirs: [String],
7475
}
7576
}
7677
}
78+
// Build table mapping protocol name (inheritedType) => mock entity (entity)
79+
for entity in parentMocks.values {
80+
for inheritedType in entity.entityNode.inheritedTypes {
81+
parentMocksByInheritedType[inheritedType] = entity
82+
}
83+
}
7784
signpost_end(name: "Process input")
7885
let t1 = CFAbsoluteTimeGetCurrent()
7986
log("Took", t1-t0, level: .verbose)
@@ -104,27 +111,33 @@ public func generate(sourceDirs: [String],
104111
let t2 = CFAbsoluteTimeGetCurrent()
105112
log("Took", t2-t1, level: .verbose)
106113

107-
let typeKeyList = [
108-
parentMocks.compactMap { (key, value) -> String? in
109-
if value.entityNode.mayHaveGlobalActor {
110-
return nil
114+
let typeKeyList = parentMocks.compactMap { (className, value) -> (String, String)? in
115+
if value.entityNode.mayHaveGlobalActor {
116+
return nil
117+
}
118+
let protocolName = className.components(separatedBy: "Mock").first ?? className
119+
return (protocolName, "\(className)()")
120+
} + annotatedProtocolMap
121+
.lazy
122+
.filter { _, entity in !entity.entityNode.mayHaveGlobalActor }
123+
.map { typeName, entity in
124+
switch entity.metadata?.nameOverride {
125+
case .none:
126+
(typeName, "\(typeName)Mock()")
127+
case .some(let nameOverride):
128+
(typeName, "\(nameOverride)()")
111129
}
112-
return key.components(separatedBy: "Mock").first
113-
},
114-
annotatedProtocolMap.filter { !$0.value.entityNode.mayHaveGlobalActor }.map(\.key)
115-
]
116-
.flatMap { $0 }
117-
.map { typeName in
118-
// nameOverride does not work correctly but it giving up.
119-
return (typeName, "\(typeName)Mock()")
130+
120131
}
132+
121133
SwiftType.customDefaultValueMap = [String: String](typeKeyList, uniquingKeysWith: { $1 })
122134

123135
signpost_begin(name: "Generate models")
124136
log("Resolve inheritance and generate unique entity models...", level: .info)
125137
generateUniqueModels(protocolMap: protocolMap,
126138
annotatedProtocolMap: annotatedProtocolMap,
127139
inheritanceMap: parentMocks,
140+
inheritanceByProtocolMap: parentMocksByInheritedType,
128141
completion: { container in
129142
resolvedEntities.append(container.entity)
130143
relevantPaths.append(contentsOf: container.paths)

Sources/MockoloFramework/Operations/UniqueModelGenerator.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import Algorithms
2121
func generateUniqueModels(protocolMap: [String: Entity],
2222
annotatedProtocolMap: [String: Entity],
2323
inheritanceMap: [String: Entity],
24+
inheritanceByProtocolMap: [String: Entity],
2425
completion: @escaping (ResolvedEntityContainer) -> ()) {
2526
scan(annotatedProtocolMap) { (key, val, lock) in
26-
let ret = generateUniqueModels(key: key, entity: val, protocolMap: protocolMap, inheritanceMap: inheritanceMap)
27+
let ret = generateUniqueModels(key: key, entity: val, protocolMap: protocolMap, inheritanceMap: inheritanceMap, inheritanceByProtocolMap: inheritanceByProtocolMap)
2728
lock?.lock()
2829
completion(ret)
2930
lock?.unlock()
@@ -33,8 +34,9 @@ func generateUniqueModels(protocolMap: [String: Entity],
3334
private func generateUniqueModels(key: String,
3435
entity: Entity,
3536
protocolMap: [String: Entity],
36-
inheritanceMap: [String: Entity]) -> ResolvedEntityContainer {
37-
let (models, processedModels, attributes, inheritedTypes, paths) = lookupEntities(key: key, declKind: entity.entityNode.declKind, protocolMap: protocolMap, inheritanceMap: inheritanceMap)
37+
inheritanceMap: [String: Entity],
38+
inheritanceByProtocolMap: [String: Entity]) -> ResolvedEntityContainer {
39+
let (models, processedModels, attributes, inheritedTypes, paths) = lookupEntities(key: key, declKind: entity.entityNode.declKind, protocolMap: protocolMap, inheritanceMap: inheritanceMap, inheritanceByProtocolMap: inheritanceByProtocolMap)
3840

3941
let processedFullNames = processedModels.compactMap {$0.fullName}
4042

Sources/MockoloFramework/Parsers/SourceParser.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class SourceParser {
3232
fileMacro: String?,
3333
completion: @escaping ([Entity], ImportMap?) -> ()) {
3434
scan(paths) { (path, lock) in
35-
self.generateASTs(path, annotation: "", fileMacro: fileMacro, declType: .classType, lock: lock, completion: completion)
35+
self.generateASTs(path, annotation: "", fileMacro: fileMacro, declType: .classType, scanAsMockfile: true, lock: lock, completion: completion)
3636
}
3737
}
3838
/// Parses decls (protocol, class) with annotations (/// @mockable) and calls a completion block
@@ -68,6 +68,7 @@ class SourceParser {
6868
annotation: String,
6969
fileMacro: String?,
7070
declType: FindTargetDeclType,
71+
scanAsMockfile: Bool = false,
7172
lock: NSLock?,
7273
completion: @escaping ([Entity], ImportMap?) -> ()) {
7374

@@ -87,7 +88,7 @@ class SourceParser {
8788

8889
var results = [Entity]()
8990
let node = Parser.parse(path)
90-
let treeVisitor = EntityVisitor(path, annotation: annotation, fileMacro: fileMacro, declType: declType)
91+
let treeVisitor = EntityVisitor(path, annotation: annotation, fileMacro: fileMacro, declType: declType, scanAsMockfile: scanAsMockfile)
9192
treeVisitor.walk(node)
9293
let ret = treeVisitor.entities
9394
results.append(contentsOf: ret)

Sources/MockoloFramework/Parsers/SwiftSyntaxExtensions.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -724,11 +724,13 @@ final class EntityVisitor: SyntaxVisitor {
724724
let fileMacro: String
725725
let path: String
726726
let declType: FindTargetDeclType
727-
init(_ path: String, annotation: String = "", fileMacro: String?, declType: FindTargetDeclType) {
727+
let scanAsMockfile: Bool
728+
init(_ path: String, annotation: String = "", fileMacro: String?, declType: FindTargetDeclType, scanAsMockfile: Bool = false) {
728729
self.annotation = annotation
729730
self.fileMacro = fileMacro ?? ""
730731
self.path = path
731732
self.declType = declType
733+
self.scanAsMockfile = scanAsMockfile
732734
super.init(viewMode: .sourceAccurate)
733735
}
734736

@@ -749,7 +751,7 @@ final class EntityVisitor: SyntaxVisitor {
749751
}
750752

751753
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
752-
if node.nameText.hasSuffix("Mock") {
754+
if scanAsMockfile || node.nameText.hasSuffix("Mock") {
753755
// this mock class node must be public else wouldn't have compiled before
754756
if let ent = Entity.node(with: node, filepath: path, isPrivate: node.isPrivate, isFinal: false, metadata: nil, processed: true) {
755757
entities.append(ent)

Sources/MockoloFramework/Utils/InheritanceResolver.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import Algorithms
2222
/// @param key The entity name to look up
2323
/// @param protocolMap Used to look up the current entity and its inheritance types
2424
/// @param inheritanceMap Used to look up inherited types if not contained in protocolMap
25+
/// @param inheritanceByProtocolMap Used to look up names of mock implementation for protocols
2526
/// @returns a list of models representing sub-entities of the current entity, a list of models processed in dependent mock files if exists,
2627
/// cumulated attributes, cumulated inherited types, and a map of filepaths and file contents (used for import lines lookup later).
2728
func lookupEntities(key: String,
2829
declKind: NominalTypeDeclKind,
2930
protocolMap: [String: Entity],
30-
inheritanceMap: [String: Entity]) -> ([Model], [Model], [String], Set<String>, [String]) {
31+
inheritanceMap: [String: Entity],
32+
inheritanceByProtocolMap: [String: Entity]) -> ([Model], [Model], [String], Set<String>, [String]) {
3133

3234
// Used to keep track of types to be mocked
3335
var models = [Model]()
@@ -55,7 +57,7 @@ func lookupEntities(key: String,
5557
// If the protocol inherits other protocols, look up their entities as well.
5658
for parent in current.entityNode.inheritedTypes {
5759
if parent != .class, parent != .anyType, parent != .anyObject {
58-
let (parentModels, parentProcessedModels, parentAttributes, parentInheritedTypes, parentPaths) = lookupEntities(key: parent, declKind: declKind, protocolMap: protocolMap, inheritanceMap: inheritanceMap)
60+
let (parentModels, parentProcessedModels, parentAttributes, parentInheritedTypes, parentPaths) = lookupEntities(key: parent, declKind: declKind, protocolMap: protocolMap, inheritanceMap: inheritanceMap, inheritanceByProtocolMap: inheritanceByProtocolMap)
5961
models.append(contentsOf: parentModels)
6062
processedModels.append(contentsOf: parentProcessedModels)
6163
attributes.append(contentsOf: parentAttributes)
@@ -64,7 +66,7 @@ func lookupEntities(key: String,
6466
}
6567
}
6668
}
67-
} else if let parentMock = inheritanceMap["\(key)Mock"], declKind == .protocol {
69+
} else if let parentMock = inheritanceMap["\(key)Mock"] ?? inheritanceByProtocolMap[key], declKind == .protocol {
6870
// If the parent protocol is not in the protocol map, look it up in the input parent mocks map.
6971
let sub = parentMock.entityNode.subContainer(metadata: parentMock.metadata, declKind: declKind, path: parentMock.filepath, isProcessed: parentMock.isProcessed)
7072
processedModels.append(contentsOf: sub.members)

0 commit comments

Comments
 (0)