Skip to content

Commit

Permalink
Merge pull request #14 from surfstudio/SNP-1653-objc-bridge
Browse files Browse the repository at this point in the history
SNP-1653 objc-bridge macro
  • Loading branch information
NullIsOne authored Jul 3, 2024
2 parents fe6b327 + 2535354 commit bc9acc2
Show file tree
Hide file tree
Showing 15 changed files with 391 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private extension NavigationStateMacro {

static func checkDeclarationType(declaration: some DeclGroupSyntax) throws {
guard declaration.is(StructDeclSyntax.self) else {
throw DeclarationError.wrongAttaching(expected: .struct)
throw DeclarationError.wrongAttaching(expected: [.struct])
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public struct CompletionHandlerMacro: PeerMacro {
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
throw DeclarationError.wrongAttaching(expected: .protocol)
throw DeclarationError.wrongAttaching(expected: [.protocol])
}
Names.protocol = protocolDecl.name.text
try SignalsMacroGroupSupport.checkProtocolDeclaration(protocolDecl)
Expand All @@ -44,11 +44,11 @@ public struct CompletionHandlerMacro: PeerMacro {
private extension CompletionHandlerMacro {

static func createHandlerClass(with protocolFuncDecls: [FunctionDeclSyntax]) -> ClassDeclSyntax {
let privateModifier = DeclModifierSyntax(name: .keyword(.private))
let publicModifier = DeclModifierSyntax(name: .keyword(.public))
let protocolIdentifier = createProtocolIdentifier()
let memberBlock = createMemberBlock(with: protocolFuncDecls)
return .init(
modifiers: [privateModifier],
modifiers: [publicModifier],
name: .identifier(Names.class),
inheritanceClause: .init(inheritedTypes: [.init(type: protocolIdentifier)]),
memberBlock: memberBlock
Expand All @@ -66,7 +66,8 @@ private extension CompletionHandlerMacro {
for funcDecl in protocolFuncDecls {
SignalsMacroGroupSupport.createFuncDecl(
from: funcDecl,
with: createFuncBody()
with: createFuncBody(),
modifiers: [.init(name: .keyword(.public))]
)
}
}
Expand All @@ -88,9 +89,10 @@ private extension CompletionHandlerMacro {
}

static func createInit() -> InitializerDeclSyntax {
let publicModifier = DeclModifierSyntax(name: .keyword(.public))
let signature = createInitSignature()
let body = createInitBody()
return .init(signature: signature, body: body)
return .init(modifiers: [publicModifier], signature: signature, body: body)
}

static func createInitSignature() -> FunctionSignatureSyntax {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public struct MulticastMacro: PeerMacro {
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
throw DeclarationError.wrongAttaching(expected: .protocol)
throw DeclarationError.wrongAttaching(expected: [.protocol])
}
try SignalsMacroGroupSupport.checkProtocolDeclaration(protocolDecl)
Names.protocol = protocolDecl.name.text
Expand Down
165 changes: 165 additions & 0 deletions Sources/SurfMacros/Implementation/Signals/ObjcBridgeMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SurfMacrosSupport

public struct ObjcBridgeMacro: PeerMacro {

// MARK: - Names

private enum Names {
static let nsObject = "NSObject"
static let entity = "entity"

static var declaration = ""
static var bridgeClass: String {
return declaration + "ObjcBridge"
}
}

// MARK: - Macro

public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
try checkDeclaration(declaration)
let declarationName = try getDeclarationName(declaration)
Names.declaration = declarationName.text
let bridgeClass = createBridgeClass(with: declarationName)
return [.init(bridgeClass)]
}

}

// MARK: - Checks

private extension ObjcBridgeMacro {

static func checkDeclaration(_ declaration: some DeclSyntaxProtocol) throws {
let error = CustomError(description: "Generic types cannot be represented in objc.")
if let structDecl = declaration.as(StructDeclSyntax.self) {
if structDecl.genericParameterClause != nil {
throw error
}
}
if let classDecl = declaration.as(ClassDeclSyntax.self) {
if classDecl.genericParameterClause != nil {
throw error
}
}
if let enumDecl = declaration.as(EnumDeclSyntax.self) {
if enumDecl.genericParameterClause != nil {
throw error
}
}
}

}

// MARK: - Getters

private extension ObjcBridgeMacro {

static func getDeclarationName(_ declaration: some DeclSyntaxProtocol) throws -> TokenSyntax {
if let structDecl = declaration.as(StructDeclSyntax.self) {
return structDecl.name
}
if let classDecl = declaration.as(ClassDeclSyntax.self) {
return classDecl.name
}
if let protocolDecl = declaration.as(ProtocolDeclSyntax.self) {
return protocolDecl.name
}
if let enumDecl = declaration.as(EnumDeclSyntax.self) {
return enumDecl.name
}
throw DeclarationError.wrongAttaching(expected: [.class, .struct, .enum, .protocol])
}

}

// MARK: - Creations

private extension ObjcBridgeMacro {

static func createBridgeClass(with name: TokenSyntax) -> ClassDeclSyntax {
let publicModifier = createPublicModifier()
let nsObjectInheritance = createNSObjectInheritance()
let memberBlock = createBridgeClassMemberBlock()
return .init(
modifiers: [publicModifier],
name: .identifier(Names.bridgeClass),
inheritanceClause: nsObjectInheritance,
memberBlock: memberBlock
)
}

static func createNSObjectInheritance() -> InheritanceClauseSyntax {
let nsObjectType = IdentifierTypeSyntax(name: .identifier(Names.nsObject))
return .init(inheritedTypes: [.init(type: nsObjectType)])
}

static func createBridgeClassMemberBlock() -> MemberBlockSyntax {
let itemList = MemberBlockItemListSyntax {
createEntityProperty()
createInit()
}
return .init(members: itemList)
}

static func createEntityProperty() -> VariableDeclSyntax {
let publicModifier = createPublicModifier()

let pattern = IdentifierPatternSyntax(identifier: .identifier(Names.entity))
let type = TypeAnnotationSyntax(type: IdentifierTypeSyntax(name: .identifier(Names.declaration)))
let patternBinding = PatternBindingSyntax(pattern: pattern, typeAnnotation: type)

return .init(
modifiers: [publicModifier],
bindingSpecifier: .keyword(.let),
bindings: [patternBinding]
)
}

static func createInit() -> InitializerDeclSyntax {
let publicModifier = createPublicModifier()
let signature = createInitSignature()
let body = createInitBody()
return .init(modifiers: [publicModifier], signature: signature, body: body)
}

static func createInitSignature() -> FunctionSignatureSyntax {
let entityParameter = createEntityParameter()
return .init(parameterClause: .init(parameters: [entityParameter]))
}

static func createEntityParameter() -> FunctionParameterSyntax {
return .init(
firstName: .wildcardToken(),
secondName: .identifier(Names.entity),
type: IdentifierTypeSyntax(name: .identifier(Names.declaration))
)
}

static func createInitBody() -> CodeBlockSyntax {
let selfEntity = MemberAccessExprSyntax(
base: DeclReferenceExprSyntax(baseName: .keyword(.`self`)),
declName: .init(baseName: .identifier(Names.entity))
)
let entityParameter = DeclReferenceExprSyntax(baseName: .identifier(Names.entity))
let entityAssignment = InfixOperatorExprSyntax(
leftOperand: selfEntity,
operator: AssignmentExprSyntax(),
rightOperand: entityParameter
)
return .init(statements: [.init(item: .expr(.init(entityAssignment)))])
}

static func createPublicModifier() -> DeclModifierSyntax {
return .init(name: .keyword(.public))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SwiftSyntaxMacros
struct SignalsPlugin {
static let providingMacros: [Macro.Type] = [
MulticastMacro.self,
ObjcBridgeMacro.self,
CompletionHandlerMacro.self
]
}
2 changes: 2 additions & 0 deletions Sources/SurfMacros/Macros/Signals/ObjcBridge.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@attached(peer, names: suffixed(ObjcBridge))
public macro ObjcBridge() = #externalMacro(module: "SurfMacroBody", type: "ObjcBridgeMacro")
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import Foundation
import SwiftSyntax

public enum DeclarationError: Error, CustomStringConvertible {
case wrongAttaching(expected: Decls)
case wrongAttaching(expected: [Decls])
case missedModifier(decl: Decls, declName: String, expected: Modifiers)
case missedInheritance(decl: Decls, declName: String, expected: String)
case unexpectedAssociatedType
case unexpectedVariable
case unexpectedParameterClause

public var description: String {
switch self {
Expand All @@ -20,6 +21,8 @@ public enum DeclarationError: Error, CustomStringConvertible {
return "There should not be any associated types"
case .unexpectedVariable:
return "There should not be any variables"
case .unexpectedParameterClause:
return "There should not be any parameter clause"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation

public enum Decls: String, CustomStringConvertible {
public enum Decls: String, CustomStringConvertible, CaseIterable {
case `protocol`
case `class`
case `struct`
Expand Down
2 changes: 1 addition & 1 deletion Tests/SurfMacros/Router/NavigationStateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ final class NavigationStateMacroTests: XCTestCase {
"""
}
let diagnostic = DiagnosticSpec(
message: "Macro can be attached to struct only",
message: "Macro can be attached to [struct] only",
line: 1,
column: 1
)
Expand Down
39 changes: 28 additions & 11 deletions Tests/SurfMacros/Signals/CompletionHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,33 @@ import XCTest
import SurfMacroBody
import SurfMacrosSupport

private let macro = "CompletionHandler"
private let type = CompletionHandlerMacro.self
private let testMacros: [String: Macro.Type] = [macro: type]
private let testMacro = "CompletionHandler"
private let testType = CompletionHandlerMacro.self
private let testMacros: [String: Macro.Type] = [testMacro: testType]
#endif

final class CompletionHandlerMacroTests: XCTestCase {

func testWhenAttachedTypeIsNotProtocol() {
let runner = AttachedTypeTests(macro: testMacro, type: testType)
runner.testWhenWrongAttachedType(
originalSource: {
"""
@\(testMacro)
\($0) BatSignal {}
"""
},
expandedSource: {
"""
\($0) BatSignal {}
"""
},
allowedDecls: [.protocol]
)
}

func testAllWrongProtocolFormats() {
let runner = ProtocolableMacroTests(macro: macro, type: type)
runner.testWhenAttachedTypeIsNotProtocol()
let runner = ProtocolableMacroTests(macro: testMacro, type: testType)
runner.testWhenFuncHasAttribute()
runner.testWhenFuncIsAsync()
runner.testWhenFuncIsGenericWithWhereKeyword()
Expand All @@ -32,7 +49,7 @@ final class CompletionHandlerMacroTests: XCTestCase {
func testWithAllPossibleArgumentFormats() {
assertMacroExpansion(
"""
@\(macro)
@\(testMacro)
protocol BatSignal {
func call(robin: Robin)
func call(for robin: Robin)
Expand All @@ -46,18 +63,18 @@ final class CompletionHandlerMacroTests: XCTestCase {
func call(_ robin: Robin)
}
private class BatSignalHandler: BatSignal {
public class BatSignalHandler: BatSignal {
private let completion: EmptyClosure?
init(completion: EmptyClosure? = nil) {
public init(completion: EmptyClosure? = nil) {
self.completion = completion
}
func call(robin: Robin) {
public func call(robin: Robin) {
completion?()
}
func call(for robin: Robin) {
public func call(for robin: Robin) {
completion?()
}
func call(_ robin: Robin) {
public func call(_ robin: Robin) {
completion?()
}
}
Expand Down
27 changes: 22 additions & 5 deletions Tests/SurfMacros/Signals/MulticastTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,33 @@ import XCTest
import SurfMacroBody
import SurfMacrosSupport

private let macro = "Multicast"
private let type = MulticastMacro.self
private let testMacros: [String: Macro.Type] = [macro: type]
private let testMacro = "Multicast"
private let testType = MulticastMacro.self
private let testMacros: [String: Macro.Type] = [testMacro: testType]
#endif

final class MulticastMacroTests: XCTestCase {

func testWhenAttachedTypeIsNotProtocol() {
let runner = AttachedTypeTests(macro: testMacro, type: testType)
runner.testWhenWrongAttachedType(
originalSource: {
"""
@\(testMacro)
\($0) BatSignal {}
"""
},
expandedSource: {
"""
\($0) BatSignal {}
"""
},
allowedDecls: [.protocol]
)
}

func testAllWrongProtocolFormats() {
let runner = ProtocolableMacroTests(macro: macro, type: type)
runner.testWhenAttachedTypeIsNotProtocol()
let runner = ProtocolableMacroTests(macro: testMacro, type: testType)
runner.testWhenFuncHasAttribute()
runner.testWhenFuncIsAsync()
runner.testWhenFuncIsGenericWithWhereKeyword()
Expand Down
Loading

0 comments on commit bc9acc2

Please sign in to comment.