I was able to solve this by creating a bridge module that expose the implementation as macro while the implementation itself is linked as normal target so it can be seen from unit tests.
targets: [
.target(
name: "TestSwiftMacros",
dependencies: [
"TestSwiftMacrosBridge"
]
),
.macro( // Exposes the implementation as macros
name: "TestSwiftMacrosBridge",
dependencies: [
"TestSwiftMacrosImplementation",
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
.target( // The key is here, that the implementation is built as normal target
name: "TestSwiftMacrosImplementation",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
.testTarget(
name: "TestSwiftMacrosTests",
dependencies: [
"TestSwiftMacrosImplementation",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
]
),
]
Then in the bridge module, you will need to add a wrapper over the implementation
import TestSwiftMacrosImplementation
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxMacros
public struct TestSwiftMacroBridge: PeerMacro {
public static func expansion(of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext) throws -> [DeclSyntax] {
try TestSwiftMacro.expansion(of: node, providingPeersOf: declaration, in: context)
}
}
finally, update the main declaration to read the new bridge
@attached(member, names: arbitrary)
public macro TestSwift() = #externalMacro(module: "TestSwiftMacrosBridge", type: "TestSwiftMacroBridge")