Skip to content

Commit

Permalink
Merge pull request #65 from uber/es-lazy
Browse files Browse the repository at this point in the history
Optimization: Lazy model generation
  • Loading branch information
ellie authored Dec 6, 2019
2 parents de0c352 + c571ce3 commit ddc3893
Show file tree
Hide file tree
Showing 11 changed files with 344 additions and 291 deletions.
2 changes: 1 addition & 1 deletion Sources/MockoloFramework/Models/ClassModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ final class ClassModel: Model {
self.needInit = needInit
self.initParams = initParams
self.offset = offset
self.attribute = Set(attributes).joined(separator: " ")
self.attribute = Set(attributes.filter {$0.contains(String.available)}).joined(separator: " ")
self.accessControlLevelDescription = acl.isEmpty ? "" : acl + " "
self.typealiasWhitelist = typealiasWhitelist
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/MockoloFramework/Models/MethodModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ final class MethodModel: Model {

let zippedParams = zip(paramDecls, comps)
self.params = zippedParams.map { (argAst: Structure, argLabel: String) -> ParamModel in
ParamModel(argAst, label: argLabel, offset: argAst.offset, length: argAst.length, data: data, isInitializer: ast.isInitializer)
return ParamModel(argAst, label: argLabel, offset: argAst.offset, length: argAst.length, data: data, inInit: ast.isInitializer)
}

self.genericTypeParams = ast.substructures
Expand Down
12 changes: 6 additions & 6 deletions Sources/MockoloFramework/Models/ParamModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,39 +24,39 @@ final class ParamModel: Model {
var type: Type
let label: String
let isGeneric: Bool
let isInitializer: Bool
let inInit: Bool

var modelType: ModelType {
return .parameter
}

init(label: String = "", name: String, typeName: String, isGeneric: Bool = false, isInitializer: Bool = false, offset: Int64, length: Int64) {
init(label: String = "", name: String, typeName: String, isGeneric: Bool = false, inInit: Bool = false, offset: Int64, length: Int64) {
self.name = name.trimmingCharacters(in: .whitespaces)
self.type = Type(typeName.trimmingCharacters(in: .whitespaces))
let labelStr = label.trimmingCharacters(in: .whitespaces)
self.label = name != labelStr ? labelStr: ""
self.offset = offset
self.length = length
self.isGeneric = isGeneric
self.isInitializer = isInitializer
self.inInit = inInit
}

init(_ ast: Structure, label: String = "", offset: Int64, length: Int64, data: Data, isGeneric: Bool = false, isInitializer: Bool = false) {
init(_ ast: Structure, label: String = "", offset: Int64, length: Int64, data: Data, isGeneric: Bool = false, inInit: Bool = false) {
self.name = ast.name
self.offset = offset
self.length = length
// Sourcekit doesn't specify if a func arg is variadic, so look ahead for the following characters to determine.
let lookahead = data.toString(offset: offset + length, length: 3)
let isVariadic = lookahead == "..."
self.isGeneric = isGeneric
self.isInitializer = isInitializer
self.inInit = inInit
let typeArg = isGeneric ? (ast.inheritedTypes.first ?? .unknownVal) : (isVariadic ? ast.typeName + "..." : ast.typeName)
self.type = Type(typeArg)
self.label = ast.name != label ? label: ""
}

var asVarDecl: String? {
if self.isInitializer {
if self.inInit {
return applyVarTemplate(name: name, type: type)
}
return nil
Expand Down
69 changes: 26 additions & 43 deletions Sources/MockoloFramework/Models/ParsedEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ struct ResolvedEntity {

func model() -> Model {
return ClassModel(identifier: key,
acl: entity.acl,
acl: entity.entityNode.acl,
attributes: attributes,
offset: entity.offset,
offset: entity.entityNode.offset,
needInit: !hasInit,
initParams: initVars,
typealiasWhitelist: typealiasWhitelist,
Expand All @@ -45,65 +45,48 @@ struct ResolvedEntityContainer {
let imports: [(String, Data, Int64)]
}

protocol EntityNode {
var name: String { get }
var acl: String { get }
var attributesDescription: String { get }
var inheritedTypes: [String] { get }
var offset: Int64 { get }
func subContainer(overrides: [String: String]?, path: String?, data: Data?, isProcessed: Bool) -> EntityNodeSubContainer
}

final class EntityNodeSubContainer {
let attributes: [String]
let members: [Model]
let hasInit: Bool
init(attributes: [String], members: [Model], hasInit: Bool) {
self.attributes = attributes
self.members = members
self.hasInit = hasInit
}
}


/// Metadata for a type being mocked
final class Entity {
var filepath: String = ""
var data: Data? = nil

let name: String
let members: [Model]
let offset: Int64
let acl: String
let attributes: [String]
let inheritedTypes: [String]
let hasInit: Bool
let isAnnotated: Bool
let overrides: [String: String]?
let entityNode: EntityNode
let isProcessed: Bool

init(name: String,
init(entityNode: EntityNode,
filepath: String = "",
data: Data? = nil,
isAnnotated: Bool,
overrides: [String: String]?,
acl: String,
attributes: [String],
inheritedTypes: [String],
members: [Model],
hasInit: Bool,
offset: Int64,
isProcessed: Bool) {
self.name = name
self.entityNode = entityNode
self.filepath = filepath
self.data = data
self.acl = acl
self.attributes = attributes
self.inheritedTypes = inheritedTypes
self.hasInit = hasInit
self.isAnnotated = isAnnotated
self.overrides = overrides
self.isProcessed = isProcessed
self.offset = offset
self.members = members
}


func subAttributes() -> [String]? {
if isProcessed {
return nil
}
return attributes.filter {$0.contains(String.available)}
}

static func model(for element: Structure, filepath: String, data: Data, overrides: [String: String]?, processed: Bool = false) -> Model? {
if element.isVariable {
return VariableModel(element, filepath: filepath, data: data, processed: processed)
} else if element.isMethod {
return MethodModel(element, filepath: filepath, data: data, processed: processed)
} else if element.isAssociatedType {
return TypeAliasModel(element, filepath: filepath, data: data, overrideTypes: overrides, processed: processed)
}

return nil
}
}
6 changes: 3 additions & 3 deletions Sources/MockoloFramework/Operations/Generator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public func generate(sourceDirs: [String]?,
semaphore: sema,
queue: mockgenQueue) { (elements, imports) in
elements.forEach { element in
parentMocks[element.name] = element
parentMocks[element.entityNode.name] = element
}

for (path, modules) in imports {
Expand All @@ -94,9 +94,9 @@ public func generate(sourceDirs: [String]?,
semaphore: sema,
queue: mockgenQueue) { (elements, imports) in
elements.forEach { element in
protocolMap[element.name] = element
protocolMap[element.entityNode.name] = element
if element.isAnnotated {
annotatedProtocolMap[element.name] = element
annotatedProtocolMap[element.entityNode.name] = element
}
}
if let imports = imports {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,11 @@ private func generateProcessedModels(_ path: String,
let topstructure = try Structure(path: path)
let subs = topstructure.substructures
let results = subs.compactMap { current -> Entity? in
let members = current.substructures.compactMap { (child: Structure) -> Model? in
return Entity.model(for: child, filepath: path, data: content, overrides: nil, processed: true)
}

let curAttributes = current.extractAttributes(content, filterOn: SwiftDeclarationAttributeKind.available.rawValue)
let hasInit = current.substructures.filter(path: \.isInitializer).count > 0
return Entity(name: current.name,
return Entity(entityNode: current,
filepath: path,
data: content,
isAnnotated: false,
overrides: nil,
acl: current.accessControlLevelDescription,
attributes: curAttributes,
inheritedTypes: [],
members: members,
hasInit: hasInit,
offset: current.offset,
isProcessed: true)
}

Expand Down
20 changes: 1 addition & 19 deletions Sources/MockoloFramework/Operations/ProtocolMapGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,30 +159,12 @@ private func generateProtcolMap(_ path: String,
if current.isProtocol {
let metadata = current.annotationMetadata(with: annotationData, in: content)
let isAnnotated = metadata != nil
let members = current.substructures.compactMap { (child: Structure) -> Model? in
return Entity.model(for: child, filepath: path, data: content, overrides: metadata?.typealiases, processed: false)
}

var attributes = current.substructures.compactMap { (child: Structure) -> [String]? in
return child.extractAttributes(content, filterOn: SwiftDeclarationAttributeKind.available.rawValue)
}.flatMap {$0}

let curAttributes = current.extractAttributes(content, filterOn: SwiftDeclarationAttributeKind.available.rawValue)
attributes.append(contentsOf: curAttributes)

let hasInit = current.substructures.filter(path: \.isInitializer).count > 0

let node = Entity(name: current.name,
let node = Entity(entityNode: current,
filepath: path,
data: content,
isAnnotated: isAnnotated,
overrides: metadata?.typealiases,
acl: current.accessControlLevelDescription,
attributes: attributes,
inheritedTypes: current.inheritedTypes,
members: members,
hasInit: hasInit,
offset: current.offset,
isProcessed: false)
results.append(node)
}
Expand Down
21 changes: 11 additions & 10 deletions Sources/MockoloFramework/Utils/InheritanceResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,17 @@ func lookupEntities(key: String,

// Look up the mock entities of a protocol specified by the name.
if let current = protocolMap[key] {

models.append(contentsOf: current.members)
if let curAttributes = current.subAttributes() {
attributes.append(contentsOf: curAttributes)
let sub = current.entityNode.subContainer(overrides: current.overrides, path: current.filepath, data: current.data, isProcessed: current.isProcessed)
models.append(contentsOf: sub.members)
if !current.isProcessed {
attributes.append(contentsOf: sub.attributes)
}
if let data = current.data {
pathToContents.append((current.filepath, data, current.offset))
pathToContents.append((current.filepath, data, current.entityNode.offset))
}
paths.append(current.filepath)
// If the protocol inherits other protocols, look up their entities as well.
for parent in current.inheritedTypes {
for parent in current.entityNode.inheritedTypes {
if parent != .class, parent != .any, parent != .anyObject {
let (parentModels, parentProcessedModels, parentAttributes, parentPaths, parentPathToContents) = lookupEntities(key: parent,
protocolMap: protocolMap,
Expand All @@ -66,12 +66,13 @@ func lookupEntities(key: String,

} else if let parentMock = inheritanceMap["\(key)Mock"] {
// If the parent protocol is not in the protocol map, look it up in the input parent mocks map.
processedModels.append(contentsOf: parentMock.members)
if let parentAttributes = parentMock.subAttributes() {
attributes.append(contentsOf: parentAttributes)
let sub = parentMock.entityNode.subContainer(overrides: parentMock.overrides, path: parentMock.filepath, data: parentMock.data, isProcessed: parentMock.isProcessed)
processedModels.append(contentsOf: sub.members)
if !parentMock.isProcessed {
attributes.append(contentsOf: sub.attributes)
}
if let data = parentMock.data {
pathToContents.append((parentMock.filepath, data, parentMock.offset))
pathToContents.append((parentMock.filepath, data, parentMock.entityNode.offset))
}
paths.append(parentMock.filepath)
}
Expand Down
48 changes: 47 additions & 1 deletion Sources/MockoloFramework/Utils/SourceKitExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct AnnotationMetadata {
var typealiases: [String: String]?
}

extension Structure {
extension Structure: EntityNode {

init(path: String) throws {
self.init(sourceKitResponse: try Request.customRequest(request: [
Expand Down Expand Up @@ -105,6 +105,52 @@ extension Structure {
return result
}

func subContainer(overrides: [String: String]?, path: String?, data: Data?, isProcessed: Bool) -> EntityNodeSubContainer {
let memberList = members(with: path, data: data, overrides: overrides, processed: isProcessed)
let subAttributes = memberAttributes(with: data)
return EntityNodeSubContainer(attributes: subAttributes, members: memberList, hasInit: hasInitMember)
}

func members(with path: String?, data: Data?, overrides: [String: String]?, processed: Bool) -> [Model] {
guard let path = path, let data = data else { return [] }
return self.substructures.compactMap { (child: Structure) -> Model? in
return model(for: child, filepath: path, data: data, overrides: overrides, processed: processed)
}
}

func memberAttributes(with data: Data?) -> [String] {
guard let data = data else { return [] }
return self.substructures.compactMap { (child: Structure) -> [String]? in
return child.extractAttributes(data, filterOn: SwiftDeclarationAttributeKind.available.rawValue)
}.flatMap {$0}
}

func model(for element: Structure, filepath: String, data: Data, overrides: [String: String]?, processed: Bool = false) -> Model? {
if element.isVariable {
return VariableModel(element, filepath: filepath, data: data, processed: processed)
} else if element.isMethod {
return MethodModel(element, filepath: filepath, data: data, processed: processed)
} else if element.isAssociatedType {
return TypeAliasModel(element, filepath: filepath, data: data, overrideTypes: overrides, processed: processed)
}

return nil
}


var acl: String {
return accessControlLevelDescription
}

var attributesDescription: String {
return attributes?.description ?? ""
}


var hasInitMember: Bool {
return self.substructures.filter(path: \.isInitializer).count > 0
}

var name: String {
// A type must have a name.
return dictionary["key.name"] as? String ?? .unknownVal
Expand Down
Loading

0 comments on commit ddc3893

Please sign in to comment.