Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update MacroTesting example tests #10

Merged
merged 3 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import MacroTesting
import XCTest

final class DefaultFatalErrorImplementationMacroTests: BaseTestCase {
override func invokeTest() {
withMacroTesting(
macros: ["defaultFatalErrorImplementation": DefaultFatalErrorImplementationMacro.self]
) {
super.invokeTest()
}
}

func testExpansionWhenAttachedToProtocolExpandsCorrectly() {
assertMacro {
"""
@defaultFatalErrorImplementation
protocol MyProtocol {
func foo()
func bar() -> Int
}
"""
} expansion: {
"""
protocol MyProtocol {
func foo()
func bar() -> Int
}

extension MyProtocol {
func foo() {
fatalError("whoops 😅")
}
func bar() -> Int {
fatalError("whoops 😅")
}
}
"""
}
}

func testExpansionWhenNotAttachedToProtocolProducesDiagnostic() {
assertMacro {
"""
@defaultFatalErrorImplementation
class MyClass {}
"""
} diagnostics: {
"""
@defaultFatalErrorImplementation
┬───────────────────────────────
╰─ 🛑 Macro `defaultFatalErrorImplementation` can only be applied to a protocol
class MyClass {}
"""
}
}

func testExpansionWhenAttachedToEmptyProtocolDoesNotAddExtension() {
assertMacro {
"""
@defaultFatalErrorImplementation
protocol EmptyProtocol {}
"""
} expansion: {
"""
protocol EmptyProtocol {}
"""
}
}
}
97 changes: 40 additions & 57 deletions Tests/MacroTestingTests/MacroExamples/AddAsyncMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public struct AddAsyncMacro: PeerMacro {
) throws -> [DeclSyntax] {

// Only on functions at the moment.
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
guard var funcDecl = declaration.as(FunctionDeclSyntax.self) else {
throw CustomError.message("@addAsync only works on functions")
}

Expand All @@ -42,30 +42,23 @@ public struct AddAsyncMacro: PeerMacro {
}

// This only makes sense void functions
if funcDecl.signature.returnClause?.type.with(\.leadingTrivia, []).with(\.trailingTrivia, [])
.description != "Void"
{
if funcDecl.signature.returnClause?.type.as(IdentifierTypeSyntax.self)?.name.text != "Void" {
throw CustomError.message(
"@addAsync requires an function that returns void"
)
}

// Requires a completion handler block as last parameter
guard
let completionHandlerParameterAttribute = funcDecl.signature.parameterClause.parameters.last?
.type.as(AttributedTypeSyntax.self),
let completionHandlerParameter = completionHandlerParameterAttribute.baseType.as(
FunctionTypeSyntax.self)
guard let completionHandlerParameterAttribute = funcDecl.signature.parameterClause.parameters.last?.type.as(AttributedTypeSyntax.self),
let completionHandlerParameter = completionHandlerParameterAttribute.baseType.as(FunctionTypeSyntax.self)
else {
throw CustomError.message(
"@addAsync requires an function that has a completion handler as last parameter"
)
}

// Completion handler needs to return Void
if completionHandlerParameter.returnClause.type.with(\.leadingTrivia, []).with(
\.trailingTrivia, []
).description != "Void" {
if completionHandlerParameter.returnClause.type.as(IdentifierTypeSyntax.self)?.name.text != "Void" {
throw CustomError.message(
"@addAsync requires an function that has a completion handler that returns Void"
)
Expand All @@ -74,18 +67,16 @@ public struct AddAsyncMacro: PeerMacro {
let returnType = completionHandlerParameter.parameters.first?.type

let isResultReturn = returnType?.children(viewMode: .all).first?.description == "Result"
let successReturnType =
isResultReturn
? returnType!.as(IdentifierTypeSyntax.self)!.genericArgumentClause?.arguments.first!.argument
: returnType
let successReturnType = isResultReturn ? returnType!.as(IdentifierTypeSyntax.self)!.genericArgumentClause?.arguments.first!.argument : returnType

// Remove completionHandler and comma from the previous parameter
var newParameterList = funcDecl.signature.parameterClause.parameters
newParameterList.removeLast()
let newParameterListLastParameter = newParameterList.last!
var newParameterListLastParameter = newParameterList.last!
newParameterList.removeLast()
newParameterList.append(
newParameterListLastParameter.with(\.trailingTrivia, []).with(\.trailingComma, nil))
newParameterListLastParameter.trailingTrivia = []
newParameterListLastParameter.trailingComma = nil
newParameterList.append(newParameterListLastParameter)

// Drop the @addAsync attribute from the new declaration.
let newAttributeList = funcDecl.attributes.filter {
Expand Down Expand Up @@ -132,44 +123,36 @@ public struct AddAsyncMacro: PeerMacro {

"""

let newFunc =
funcDecl
.with(
\.signature,
funcDecl.signature
.with(
\.effectSpecifiers,
FunctionEffectSpecifiersSyntax(
leadingTrivia: .space,
asyncSpecifier: .keyword(.async),
throwsSpecifier: isResultReturn ? .keyword(.throws) : nil
) // add async
)
.with(
\.returnClause,
successReturnType != nil
? ReturnClauseSyntax(
leadingTrivia: .space, type: successReturnType!.with(\.leadingTrivia, .space)) : nil
) // add result type
.with(
\.parameterClause,
funcDecl.signature.parameterClause.with(\.parameters, newParameterList) // drop completion handler
.with(\.trailingTrivia, [])
)
)
.with(
\.body,
CodeBlockSyntax(
leftBrace: .leftBraceToken(leadingTrivia: .space),
statements: CodeBlockItemListSyntax(
[CodeBlockItemSyntax(item: .expr(newBody))]
),
rightBrace: .rightBraceToken(leadingTrivia: .newline)
)
)
.with(\.attributes, newAttributeList)
.with(\.leadingTrivia, .newlines(2))
// add async
funcDecl.signature.effectSpecifiers = FunctionEffectSpecifiersSyntax(
leadingTrivia: .space,
asyncSpecifier: .keyword(.async),
throwsSpecifier: isResultReturn ? .keyword(.throws) : nil
)

// add result type
if let successReturnType {
funcDecl.signature.returnClause = ReturnClauseSyntax(leadingTrivia: .space, type: successReturnType.with(\.leadingTrivia, .space))
} else {
funcDecl.signature.returnClause = nil
}

// drop completion handler
funcDecl.signature.parameterClause.parameters = newParameterList
funcDecl.signature.parameterClause.trailingTrivia = []

funcDecl.body = CodeBlockSyntax(
leftBrace: .leftBraceToken(leadingTrivia: .space),
statements: CodeBlockItemListSyntax(
[CodeBlockItemSyntax(item: .expr(newBody))]
),
rightBrace: .rightBraceToken(leadingTrivia: .newline)
)

funcDecl.attributes = newAttributeList

funcDecl.leadingTrivia = .newlines(2)

return [DeclSyntax(newFunc)]
return [DeclSyntax(funcDecl)]
}
}
16 changes: 4 additions & 12 deletions Tests/MacroTestingTests/MacroExamples/AddBlocker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public struct AddBlocker: ExpressionMacro {
_ node: InfixOperatorExprSyntax
) -> ExprSyntax {
// Identify any infix operator + in the tree.
if let binOp = node.operator.as(BinaryOperatorExprSyntax.self) {
if var binOp = node.operator.as(BinaryOperatorExprSyntax.self) {
if binOp.operator.text == "+" {
// Form the warning
let messageID = MessageID(domain: "silly", id: "addblock")
Expand Down Expand Up @@ -72,17 +72,9 @@ public struct AddBlocker: ExpressionMacro {
)
)

return ExprSyntax(
node.with(
\.operator,
ExprSyntax(
binOp.with(
\.operator,
binOp.operator.with(\.tokenKind, .binaryOperator("-"))
)
)
)
)
binOp.operator.tokenKind = .binaryOperator("-")

return ExprSyntax(node.with(\.operator, ExprSyntax(binOp)))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,22 @@ public struct AddCompletionHandlerMacro: PeerMacro {
) throws -> [DeclSyntax] {
// Only on functions at the moment. We could handle initializers as well
// with a bit of work.
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
guard var funcDecl = declaration.as(FunctionDeclSyntax.self) else {
throw CustomError.message("@addCompletionHandler only works on functions")
}

// This only makes sense for async functions.
if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil {
let newEffects: FunctionEffectSpecifiersSyntax
var newEffects: FunctionEffectSpecifiersSyntax
if let existingEffects = funcDecl.signature.effectSpecifiers {
newEffects = existingEffects.with(\.asyncSpecifier, .keyword(.async))
newEffects = existingEffects
newEffects.asyncSpecifier = .keyword(.async)
} else {
newEffects = FunctionEffectSpecifiersSyntax(asyncSpecifier: .keyword(.async))
}

let newSignature = funcDecl.signature.with(\.effectSpecifiers, newEffects)
var newSignature = funcDecl.signature
newSignature.effectSpecifiers = newEffects
let messageID = MessageID(domain: "MacroExamples", id: "MissingAsync")

let diag = Diagnostic(
Expand Down Expand Up @@ -73,8 +75,9 @@ public struct AddCompletionHandlerMacro: PeerMacro {
}

// Form the completion handler parameter.
let resultType: TypeSyntax? = funcDecl.signature.returnClause?.type.with(\.leadingTrivia, [])
.with(\.trailingTrivia, [])
var resultType = funcDecl.signature.returnClause?.type
resultType?.leadingTrivia = []
resultType?.trailingTrivia = []

let completionHandlerParam =
FunctionParameterSyntax(
Expand All @@ -86,14 +89,12 @@ public struct AddCompletionHandlerMacro: PeerMacro {
// Add the completion handler parameter to the parameter list.
let parameterList = funcDecl.signature.parameterClause.parameters
var newParameterList = parameterList
if let lastParam = parameterList.last {
if var lastParam = parameterList.last {
// We need to add a trailing comma to the preceding list.
newParameterList.removeLast()
lastParam.trailingComma = .commaToken(trailingTrivia: .space)
newParameterList += [
lastParam.with(
\.trailingComma,
.commaToken(trailingTrivia: .space)
),
lastParam,
completionHandlerParam,
]
} else {
Expand Down Expand Up @@ -137,35 +138,28 @@ public struct AddCompletionHandlerMacro: PeerMacro {
return attributeType.name.text != nodeType.name.text
}

let newFunc =
funcDecl
.with(
\.signature,
funcDecl.signature
.with(
\.effectSpecifiers,
funcDecl.signature.effectSpecifiers?.with(\.asyncSpecifier, nil) // drop async
)
.with(\.returnClause, nil) // drop result type
.with(
\.parameterClause, // add completion handler parameter
funcDecl.signature.parameterClause.with(\.parameters, newParameterList)
.with(\.trailingTrivia, [])
)
)
.with(
\.body,
CodeBlockSyntax(
leftBrace: .leftBraceToken(leadingTrivia: .space),
statements: CodeBlockItemListSyntax(
[CodeBlockItemSyntax(item: .expr(newBody))]
),
rightBrace: .rightBraceToken(leadingTrivia: .newline)
)
)
.with(\.attributes, newAttributeList)
.with(\.leadingTrivia, .newlines(2))
// drop async
funcDecl.signature.effectSpecifiers?.asyncSpecifier = nil

// drop result type
funcDecl.signature.returnClause = nil

// add completion handler parameter
funcDecl.signature.parameterClause.parameters = newParameterList
funcDecl.signature.parameterClause.trailingTrivia = []

funcDecl.body = CodeBlockSyntax(
leftBrace: .leftBraceToken(leadingTrivia: .space),
statements: CodeBlockItemListSyntax(
[CodeBlockItemSyntax(item: .expr(newBody))]
),
rightBrace: .rightBraceToken(leadingTrivia: .newline)
)

funcDecl.attributes = newAttributeList

funcDecl.leadingTrivia = .newlines(2)

return [DeclSyntax(newFunc)]
return [DeclSyntax(funcDecl)]
}
}
12 changes: 4 additions & 8 deletions Tests/MacroTestingTests/MacroExamples/CustomCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,18 @@ public enum CustomCodable: MemberMacro {
let cases = memberList.compactMap({ member -> String? in
// is a property
guard
let propertyName = member.decl.as(VariableDeclSyntax.self)?.bindings.first?.pattern.as(
IdentifierPatternSyntax.self)?.identifier.text
let propertyName = member.decl.as(VariableDeclSyntax.self)?.bindings.first?.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
else {
return nil
}

// if it has a CodableKey macro on it
if let customKeyMacro = member.decl.as(VariableDeclSyntax.self)?.attributes.first(where: {
element in
element.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.description
== "CodableKey"
if let customKeyMacro = member.decl.as(VariableDeclSyntax.self)?.attributes.first(where: { element in
element.as(AttributeSyntax.self)?.attributeName.as(IdentifierTypeSyntax.self)?.description == "CodableKey"
}) {

// Uses the value in the Macro
let customKeyValue = customKeyMacro.as(AttributeSyntax.self)!.arguments!.as(
LabeledExprListSyntax.self)!.first!.expression
let customKeyValue = customKeyMacro.as(AttributeSyntax.self)!.arguments!.as(LabeledExprListSyntax.self)!.first!.expression

return "case \(propertyName) = \(customKeyValue)"
} else {
Expand Down
Loading