Skip to content

Commit

Permalink
Merge pull request #82 from uber/es-macro
Browse files Browse the repository at this point in the history
Support members wrapped in #if macro
  • Loading branch information
ellie authored Feb 12, 2020
2 parents b7c3ccd + 5a81b8b commit 64c1825
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 43 deletions.
45 changes: 45 additions & 0 deletions Sources/MockoloFramework/Models/IfMacroModel.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// 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 Foundation

final class IfMacroModel: Model {
var name: String
var offset: Int64
var type: Type
let entities: [Model]

var modelType: ModelType {
return .macro
}

var fullName: String {
return entities.map {$0.fullName}.joined(separator: "_")
}

init(name: String,
offset: Int64,
entities: [Model]) {
self.name = name
self.entities = entities
self.offset = offset
self.type = Type(name)
}

func render(with identifier: String, typeKeys: [String: String]? = nil) -> String? {
return applyMacroTemplate(name: name, typeKeys: typeKeys, entities: entities)
}
}
2 changes: 1 addition & 1 deletion Sources/MockoloFramework/Models/Model.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import Foundation

public enum ModelType {
case variable, method, typeAlias, parameter, `class`
case variable, method, typeAlias, parameter, macro, `class`
}

/// Represents a model for an entity such as var, func, class, etc.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ extension TypeInheritanceClauseSyntax {
}
}

extension MemberDeclListSyntax {

extension MemberDeclListItemSyntax {
private func validateMember(_ modifiers: ModifierListSyntax?, _ declType: DeclType, processed: Bool) -> Bool {
if let mods = modifiers {
if !processed && mods.isPrivate || mods.isStatic && declType == .classType {
Expand All @@ -126,7 +125,6 @@ extension MemberDeclListSyntax {
if processed {
return isRequired
}

var isConvenience = false
var isPrivate = false
if let modifiers = initDecl.modifiers {
Expand All @@ -142,60 +140,98 @@ extension MemberDeclListSyntax {

private func memberAcl(_ modifiers: ModifierListSyntax?, _ encloserAcl: String, _ declType: DeclType) -> String {
if declType == .protocolType {
return encloserAcl
return encloserAcl
}

return modifiers?.acl ?? ""
}

func transformToModel(with encloserAcl: String, declType: DeclType, overrides: [String: String]?, processed: Bool) -> (Model, String?, Bool)? {
if let varMember = self.decl as? VariableDeclSyntax {
if validateMember(varMember.modifiers, declType, processed: processed) {
let acl = memberAcl(varMember.modifiers, encloserAcl, declType)
if let item = varMember.models(with: acl, declType: declType, processed: processed).first {
return (item, varMember.attributes?.trimmedDescription, false)
}
}
} else if let funcMember = self.decl as? FunctionDeclSyntax {
if validateMember(funcMember.modifiers, declType, processed: processed) {
let acl = memberAcl(funcMember.modifiers, encloserAcl, declType)
let item = funcMember.model(with: acl, declType: declType, processed: processed)
return (item, funcMember.attributes?.trimmedDescription, false)
}
} else if let subscriptMember = self.decl as? SubscriptDeclSyntax {
if validateMember(subscriptMember.modifiers, declType, processed: processed) {
let acl = memberAcl(subscriptMember.modifiers, encloserAcl, declType)
let item = subscriptMember.model(with: acl, declType: declType, processed: processed)
return (item, subscriptMember.attributes?.trimmedDescription, false)
}
} else if let initMember = self.decl as? InitializerDeclSyntax {
if validateInit(initMember, declType, processed: processed) {
let acl = memberAcl(initMember.modifiers, encloserAcl, declType)
let item = initMember.model(with: acl, declType: declType, processed: processed)
return (item, initMember.attributes?.trimmedDescription, true)
}
} else if let patMember = self.decl as? AssociatedtypeDeclSyntax {
let acl = memberAcl(patMember.modifiers, encloserAcl, declType)
let item = patMember.model(with: acl, overrides: overrides, processed: processed)
return (item, patMember.attributes?.trimmedDescription, false)
} else if let taMember = self.decl as? TypealiasDeclSyntax {
let acl = memberAcl(taMember.modifiers, encloserAcl, declType)
let item = taMember.model(with: acl, overrides: overrides, processed: processed)
return (item, taMember.attributes?.trimmedDescription, false)
} else if let ifMacroMember = self.decl as? IfConfigDeclSyntax {
let (item, attr, flag) = ifMacroMember.model(with: encloserAcl, declType: declType, overrides: overrides, processed: processed)
return (item, attr, flag)
}

return nil
}
}

extension MemberDeclListSyntax {
func memberData(with encloserAcl: String, declType: DeclType, overrides: [String: String]?, processed: Bool) -> EntityNodeSubContainer {
var attributeList = [String]()
var memberList = [Model]()
var hasInit = false
var attrDesc: String? = nil

for m in self {
if let varMember = m.decl as? VariableDeclSyntax {
if validateMember(varMember.modifiers, declType, processed: processed) {
let acl = memberAcl(varMember.modifiers, encloserAcl, declType)
memberList.append(contentsOf: varMember.models(with: acl, declType: declType, processed: processed))
attrDesc = varMember.attributes?.trimmedDescription
}
} else if let funcMember = m.decl as? FunctionDeclSyntax {
if validateMember(funcMember.modifiers, declType, processed: processed) {
let acl = memberAcl(funcMember.modifiers, encloserAcl, declType)
memberList.append(funcMember.model(with: acl, declType: declType, processed: processed))
attrDesc = funcMember.attributes?.trimmedDescription
if let (item, attr, flag) = m.transformToModel(with: encloserAcl, declType: declType, overrides: overrides, processed: processed) {
memberList.append(item)
if let attrDesc = attr {
attributeList.append(attrDesc)
}
} else if let subscriptMember = m.decl as? SubscriptDeclSyntax {
if validateMember(subscriptMember.modifiers, declType, processed: processed) {
let acl = memberAcl(subscriptMember.modifiers, encloserAcl, declType)
memberList.append(subscriptMember.model(with: acl, declType: declType, processed: processed))
attrDesc = subscriptMember.attributes?.trimmedDescription
}
} else if let initMember = m.decl as? InitializerDeclSyntax {
if validateInit(initMember, declType, processed: processed) {
hasInit = true
let acl = memberAcl(initMember.modifiers, encloserAcl, declType)
memberList.append(initMember.model(with: acl, declType: declType, processed: processed))
attrDesc = initMember.attributes?.trimmedDescription
}
} else if let patMember = m.decl as? AssociatedtypeDeclSyntax {
let acl = memberAcl(patMember.modifiers, encloserAcl, declType)
memberList.append(patMember.model(with: acl, overrides: overrides, processed: processed))
attrDesc = patMember.attributes?.trimmedDescription
} else if let taMember = m.decl as? TypealiasDeclSyntax {
let acl = memberAcl(taMember.modifiers, encloserAcl, declType)
memberList.append(taMember.model(with: acl, overrides: overrides, processed: processed))
attrDesc = taMember.attributes?.trimmedDescription
hasInit = hasInit || flag
}

if let attrDesc = attrDesc {
attributeList.append(attrDesc.trimmingCharacters(in: .whitespacesAndNewlines))
}
return EntityNodeSubContainer(attributes: attributeList, members: memberList, hasInit: hasInit)
}
}

extension IfConfigDeclSyntax {
func model(with encloserAcl: String, declType: DeclType, overrides: [String: String]?, processed: Bool) -> (Model, String?, Bool) {
var subModels = [Model]()
var attrDesc: String?
var hasInit = false

var name = ""
for cl in self.clauses {
if let desc = cl.condition?.description, let list = cl.elements as? MemberDeclListSyntax {
name = desc

for element in list {
if let (item, attr, flag) = element.transformToModel(with: encloserAcl, declType: declType, overrides: overrides, processed: processed) {
subModels.append(item)
if let attr = attr, attr.contains(String.available) {
attrDesc = attr
}
hasInit = hasInit || flag
}
}
}
}

return EntityNodeSubContainer(attributes: attributeList, members: memberList, hasInit: hasInit)
let macroModel = IfMacroModel(name: name, offset: self.offset, entities: subModels)
return (macroModel, attrDesc, hasInit)
}
}

Expand Down
32 changes: 32 additions & 0 deletions Sources/MockoloFramework/Templates/IfMacroTemplate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// 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 Foundation

func applyMacroTemplate(name: String,
typeKeys: [String: String]?,
entities: [Model]) -> String {
let rendered = entities
.compactMap {$0.render(with: $0.name, typeKeys: typeKeys) }
.joined(separator: "\n")

let template = """
#if \(name)
\(rendered)
#endif
"""
return template
}
47 changes: 47 additions & 0 deletions Tests/TestMacros/FixtureMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import MockoloFramework


let macro =
"""
/// \(String.mockAnnotation)
protocol PresentableListener: class {
func run()
#if DEBUG
func showDebugMode()
#endif
}
"""

let macroMock = """
class PresentableListenerMock: PresentableListener {
private var _doneInit = false
init() { _doneInit = true }
var runCallCount = 0
var runHandler: (() -> ())?
func run() {
runCallCount += 1
if let runHandler = runHandler {
runHandler()
}
}
#if DEBUG
var showDebugModeCallCount = 0
var showDebugModeHandler: (() -> ())?
func showDebugMode() {
showDebugModeCallCount += 1
if let showDebugModeHandler = showDebugModeHandler {
showDebugModeHandler()
}
}
#endif
}
"""
9 changes: 9 additions & 0 deletions Tests/TestMacros/MacroTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

class MacroTests: MockoloTestCase {
func testMacro() {
verify(srcContent: macro,
dstContent: macroMock,
parser: .swiftSyntax)
}
}

0 comments on commit 64c1825

Please sign in to comment.