Skip to content

Commit

Permalink
Reduce test case
Browse files Browse the repository at this point in the history
  • Loading branch information
kimdv committed Oct 31, 2023
1 parent 71a3874 commit 189da84
Showing 1 changed file with 40 additions and 132 deletions.
172 changes: 40 additions & 132 deletions Tests/SwiftSyntaxMacroExpansionTest/PeerMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,117 +28,51 @@ import XCTest
final class PeerMacroTests: XCTestCase {
private let indentationWidth: Trivia = .spaces(2)

fileprivate struct AddCompletionHandler: PeerMacro {
static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) 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 {
throw MacroExpansionErrorMessage("@addCompletionHandler only works on functions")
}

// This only makes sense for async functions.
if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil {
let newEffects: FunctionEffectSpecifiersSyntax
if let existingEffects = funcDecl.signature.effectSpecifiers {
newEffects = existingEffects.with(\.asyncSpecifier, .keyword(.async))
} else {
newEffects = FunctionEffectSpecifiersSyntax(asyncSpecifier: .keyword(.async))
func testAddCompletionHandler() {
struct TestMacro: PeerMacro {
static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) 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 {
throw MacroExpansionErrorMessage("@addCompletionHandler only works on functions")
}

let newSignature = funcDecl.signature.with(\.effectSpecifiers, newEffects)

let diag = Diagnostic(
node: Syntax(funcDecl.funcKeyword),
message: MacroExpansionErrorMessage(
"can only add a completion-handler variant to an 'async' function"
),
fixIts: [
FixIt(
message: MacroExpansionFixItMessage(
"add 'async'"
),
changes: [
FixIt.Change.replace(
oldNode: Syntax(funcDecl.signature),
newNode: Syntax(newSignature)
)
]
)
]
)

context.diagnose(diag)
return []
}

// Form the completion handler parameter.
let resultType: TypeSyntax? = funcDecl.signature.returnClause?.type.trimmed

let completionHandlerParam =
FunctionParameterSyntax(
firstName: .identifier("completionHandler"),
colon: .colonToken(trailingTrivia: .space),
type: TypeSyntax("(\(resultType ?? "")) -> Void")
)

// Add the completion handler parameter to the parameter list.
let parameterList = funcDecl.signature.parameterClause.parameters
var newParameterList = parameterList
if !parameterList.isEmpty {
// We need to add a trailing comma to the preceding list.
newParameterList[newParameterList.index(before: newParameterList.endIndex)].trailingComma = .commaToken(trailingTrivia: .space)
}
newParameterList.append(completionHandlerParam)

let callArguments: [String] = parameterList.map { param in
let argName = param.secondName ?? param.firstName

if param.firstName.text != "_" {
return "\(param.firstName.text): \(argName.text)"
// This only makes sense for async functions.
if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil {
let newEffects = FunctionEffectSpecifiersSyntax(asyncSpecifier: .keyword(.async))
let newSignature = funcDecl.signature.with(\.effectSpecifiers, newEffects)

let diag = Diagnostic(
node: Syntax(funcDecl.funcKeyword),
message: MacroExpansionErrorMessage(
"can only add a completion-handler variant to an 'async' function"
),
fixIts: [
FixIt(
message: MacroExpansionFixItMessage(
"add 'async'"
),
changes: [
FixIt.Change.replace(
oldNode: Syntax(funcDecl.signature),
newNode: Syntax(newSignature)
)
]
)
]
)

context.diagnose(diag)
return []
}

return "\(argName.text)"
}

let call: ExprSyntax =
"\(funcDecl.name)(\(raw: callArguments.joined(separator: ", ")))"

// FIXME: We should make CodeBlockSyntax ExpressibleByStringInterpolation,
// so that the full body could go here.
let newBody: ExprSyntax =
"""
Task {
completionHandler(await \(call))
}
"""

// Drop the @addCompletionHandler attribute from the new declaration.
let newAttributeList = funcDecl.attributes.filter {
guard case let .attribute(attribute) = $0 else {
return true
}
return attribute.attributeName.as(IdentifierTypeSyntax.self)?.name == "addCompletionHandler"
return []
}

var newFunc = funcDecl
newFunc.signature.effectSpecifiers?.asyncSpecifier = nil // drop async
newFunc.signature.returnClause = nil // drop result type
newFunc.signature.parameterClause.parameters = newParameterList
newFunc.signature.parameterClause.trailingTrivia = []
newFunc.body = CodeBlockSyntax { newBody }
newFunc.attributes = newAttributeList

return [DeclSyntax(newFunc)]
}
}

func testAddCompletionHandler() {
assertMacroExpansion(
"""
@addCompletionHandler
Expand All @@ -153,7 +87,7 @@ final class PeerMacroTests: XCTestCase {
}
}
""",
macros: ["addCompletionHandler": AddCompletionHandler.self],
macros: ["addCompletionHandler": TestMacro.self],
indentationWidth: indentationWidth
)
}
Expand Down Expand Up @@ -221,30 +155,4 @@ final class PeerMacroTests: XCTestCase {
]
)
}

func testAddCompletionHandlerWhereThereIsNotAsync() {
assertMacroExpansion(
"""
@addCompletionHandler
func f(a: Int, for b: String, _ value: Double) -> String { }
""",
expandedSource: """
func f(a: Int, for b: String, _ value: Double) -> String { }
""",
diagnostics: [
DiagnosticSpec(
message: "can only add a completion-handler variant to an 'async' function",
line: 2,
column: 1,
fixIts: [FixItSpec(message: "add 'async'")]
)
],
macros: ["addCompletionHandler": AddCompletionHandler.self],
fixedSource: """
@addCompletionHandler
func f(a: Int, for b: String, _ value: Double) async-> String { }
""",
indentationWidth: indentationWidth
)
}
}

0 comments on commit 189da84

Please sign in to comment.