Skip to content

Commit

Permalink
Fix: inheritance for modules and UI component from other classes
Browse files Browse the repository at this point in the history
  • Loading branch information
ikhvorost committed Dec 10, 2023
1 parent 24b7705 commit 9da9a4c
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 92 deletions.
25 changes: 14 additions & 11 deletions Sources/ReactBridgeMacros/ErrorMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,29 +54,32 @@ enum ErrorMessage: DiagnosticMessage {
}

var message: String {
switch self {
let message = switch self {
// Errors
case .funcOnly(let macroName):
return "@\(macroName) can only be applied to a func"
"@\(macroName) can only be applied to a func"
case .classOnly(let macroName):
return "@\(macroName) can only be applied to a class"
"@\(macroName) can only be applied to a class"
case .varOnly(let macroName):
return "@\(macroName) can only be applied to a var"
"@\(macroName) can only be applied to a var"
case .varSingleOnly(let macroName):
return "@\(macroName) can only be applied to a single var"
"@\(macroName) can only be applied to a single var"
case .objcOnly(let name):
return "'\(name)' must be marked with '@objc'"
"'\(name)' must be marked with '@objc'"
case .mustInherit(let className, let superclassName):
return "'\(className)' must inherit '\(superclassName)'"
"'\(className)' must inherit '\(superclassName)'"
case .mustConform(let className, let protocolName):
return "'\(className)' must conform '\(protocolName)'"
"'\(className)' must conform '\(protocolName)'"
case .mustBeClass:
return "Return type must be any class type or 'Any'"
"Return type must be any class type or 'Any'"
case .unsupportedType(let typeName):
return "'\(typeName)' type is not supported"
"'\(typeName)' type is not supported"

// Warnings
case .nonSync:
return "Functions with a defined return type should be synchronous"
"Functions with a defined return type should be synchronous"
}
return "ReactBridge: \(message)"
}

var diagnosticID: MessageID {
Expand Down
5 changes: 1 addition & 4 deletions Sources/ReactBridgeMacros/Extensions/ExprSyntax.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,6 @@ extension ExprSyntax {
}

var boolValue: Bool? {
guard let tokenKind = self.as(BooleanLiteralExprSyntax.self)?.literal.tokenKind else {
return nil
}
return tokenKind == .keyword(.true)
self.as(BooleanLiteralExprSyntax.self)?.literal.tokenKind == .keyword(.true)
}
}
18 changes: 11 additions & 7 deletions Sources/ReactBridgeMacros/ReactModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,18 @@ extension ReactModule: MemberMacro {

let className = "\(classDecl.name.trimmed)"

// Error: NSObject
guard classDecl.inheritanceClause?.description.contains("NSObject") == true else {
guard let inheritance = classDecl.inheritanceClause?.description else {
throw Diagnostic(node: classDecl.name, message: ErrorMessage.mustInherit(className: className, superclassName: "NSObject"))
}

// Error: RCTBridgeModule
guard classDecl.inheritanceClause?.description.contains("RCTBridgeModule") == true else {
throw Diagnostic(node: classDecl.name, message: ErrorMessage.mustConform(className: className, protocolName: "RCTBridgeModule"))
var override = false
if inheritance.contains("NSObject") {
if inheritance.contains("RCTBridgeModule") == false {
throw Diagnostic(node: classDecl.name, message: ErrorMessage.mustConform(className: className, protocolName: "RCTBridgeModule"))
}
}
else {
override = true
}

let arguments = node.arguments()
Expand All @@ -94,8 +98,8 @@ extension ReactModule: MemberMacro {
let mainQueueSetup = arguments["requiresMainQueueSetup"]?.boolValue == true

var items: [DeclSyntax] = [
moduleName(name: jsName),
requiresMainQueueSetup(value: mainQueueSetup),
moduleName(name: jsName, override: override),
requiresMainQueueSetup(value: mainQueueSetup, override: override),
registerModule
]

Expand Down
3 changes: 1 addition & 2 deletions Sources/ReactBridgeMacros/ReactView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ extension ReactView: MemberMacro {

let className = "\(classDecl.name.trimmed)"

// Error: RCTViewManager
guard classDecl.inheritanceClause?.description.contains("RCTViewManager") == true else {
guard classDecl.inheritanceClause != nil else {
throw Diagnostic(node: classDecl.name, message: ErrorMessage.mustInherit(className: className, superclassName: "RCTViewManager"))
}

Expand Down
156 changes: 88 additions & 68 deletions Tests/ReactBridgeTests/ReactBridgeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -308,51 +308,68 @@ final class ReactModuleTests: XCTestCase {
"ReactModule": ReactModule.self
]

func methods(name: String, requiresMainQueueSetup: Bool = false, override: Bool = false) -> String {
"""
@objc \(override ? "override " : "")class func moduleName() -> String! {
"\(name)"
}
@objc \(override ? "override " : "")class func requiresMainQueueSetup() -> Bool {
\(requiresMainQueueSetup)
}
@objc static func _registerModule() {
RCTRegisterModule(self);
}
"""
}

func test_struct() {
let diagnostic = DiagnosticSpec(message: ErrorMessage.classOnly(macroName: "ReactModule").message, line: 1, column: 1)

assertMacroExpansion(
"""
@ReactModule
struct A {}
struct Module {}
""",
expandedSource:
"""
struct A {}
struct Module {}
""",
diagnostics: [diagnostic],
macros: macros
)
}

func test_NSObject() {
let diagnostic = DiagnosticSpec(message: ErrorMessage.mustInherit(className: "A", superclassName: "NSObject").message, line: 2, column: 7)
let diagnostic = DiagnosticSpec(message: ErrorMessage.mustInherit(className: "Module", superclassName: "NSObject").message, line: 2, column: 7)

assertMacroExpansion(
"""
@ReactModule
class A {}
class Module {}
""",
expandedSource:
"""
class A {}
class Module {}
""",
diagnostics: [diagnostic],
macros: macros
)
}

func test_RCTBridgeModule() {
let diagnostic = DiagnosticSpec(message: ErrorMessage.mustConform(className: "A", protocolName: "RCTBridgeModule").message, line: 2, column: 7)
let diagnostic = DiagnosticSpec(message: ErrorMessage.mustConform(className: "Module", protocolName: "RCTBridgeModule").message, line: 2, column: 7)

assertMacroExpansion(
"""
@ReactModule
class A: NSObject {}
class Module: NSObject {}
""",
expandedSource:
"""
class A: NSObject {}
class Module: NSObject {}
""",
diagnostics: [diagnostic],
macros: macros
Expand All @@ -363,24 +380,30 @@ final class ReactModuleTests: XCTestCase {
assertMacroExpansion(
"""
@ReactModule
class A: NSObject, RCTBridgeModule {
class Module: NSObject, RCTBridgeModule {
}
""",
expandedSource:
"""
class A: NSObject, RCTBridgeModule {
@objc class func moduleName() -> String! {
"A"
}
@objc class func requiresMainQueueSetup() -> Bool {
false
}
@objc static func _registerModule() {
RCTRegisterModule(self);
}
class Module: NSObject, RCTBridgeModule {
\(methods(name: "Module"))
}
""",
macros: macros
)
}

func test_RCTEventEmitter() {
assertMacroExpansion(
"""
@ReactModule
class Module: RCTEventEmitter {
}
""",
expandedSource:
"""
class Module: RCTEventEmitter {
\(methods(name: "Module", override: true))
}
""",
macros: macros
Expand All @@ -390,25 +413,14 @@ final class ReactModuleTests: XCTestCase {
func test_params() {
assertMacroExpansion(
"""
@ReactModule(jsName: "ModuleA", requiresMainQueueSetup: true, methodQueue: .main)
@ReactModule(jsName: "Module2", requiresMainQueueSetup: true, methodQueue: .main)
class A: NSObject, RCTBridgeModule {
}
""",
expandedSource:
"""
class A: NSObject, RCTBridgeModule {
@objc class func moduleName() -> String! {
"ModuleA"
}
@objc class func requiresMainQueueSetup() -> Bool {
true
}
@objc static func _registerModule() {
RCTRegisterModule(self);
}
\(methods(name: "Module2", requiresMainQueueSetup: true))
@objc var methodQueue: DispatchQueue {
.main
Expand Down Expand Up @@ -617,6 +629,27 @@ final class ReactViewTests: XCTestCase {
"ReactView": ReactView.self,
]

func methods(name: String) -> String {
"""
@objc static func _registerModule() {
RCTRegisterModule(self);
}
@objc override class func moduleName() -> String! {
"\(name)"
}
@objc override class func requiresMainQueueSetup() -> Bool {
true
}
@objc override var methodQueue: DispatchQueue {
.main
}
"""
}

func test_struct() {
let diagnostic = DiagnosticSpec(message: ErrorMessage.classOnly(macroName: "ReactView").message, line: 1, column: 1)

Expand All @@ -635,7 +668,7 @@ final class ReactViewTests: XCTestCase {
}

func test_RCTViewManager() {
let diagnostic = DiagnosticSpec(message: ErrorMessage.mustInherit(className: "View", superclassName: "RCTViewManager").message, line: 2, column: 7)
let diagnostic = DiagnosticSpec(message: ErrorMessage.mustInherit(className: "View", superclassName: "RCTViewManager").message, line: 2, column: 7, severity: .error)

assertMacroExpansion(
"""
Expand All @@ -661,22 +694,24 @@ final class ReactViewTests: XCTestCase {
expandedSource:
"""
class View: RCTViewManager {
@objc static func _registerModule() {
RCTRegisterModule(self);
}
@objc override class func moduleName() -> String! {
"View"
}
@objc override class func requiresMainQueueSetup() -> Bool {
true
}
@objc override var methodQueue: DispatchQueue {
.main
}
\(methods(name: "View"))
}
""",
macros: macros
)
}

func test_RNCWebViewManager() {
assertMacroExpansion(
"""
@ReactView
class View: RNCWebViewManager {
}
""",
expandedSource:
"""
class View: RNCWebViewManager {
\(methods(name: "View"))
}
""",
macros: macros
Expand All @@ -693,22 +728,7 @@ final class ReactViewTests: XCTestCase {
expandedSource:
"""
class View: RCTViewManager {
@objc static func _registerModule() {
RCTRegisterModule(self);
}
@objc override class func moduleName() -> String! {
"MyView"
}
@objc override class func requiresMainQueueSetup() -> Bool {
true
}
@objc override var methodQueue: DispatchQueue {
.main
}
\(methods(name: "MyView"))
}
""",
macros: macros
Expand Down

0 comments on commit 9da9a4c

Please sign in to comment.