Skip to content
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
10 changes: 9 additions & 1 deletion Sources/TestingMacros/ConditionMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,15 @@ extension ExitTestConditionMacro {
fatalError("Could not find the body argument to this exit test. Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new")
}

let bodyArgumentExpr = arguments[trailingClosureIndex].expression
// Extract the body argument and, if it's a closure with a capture list,
// emit an appropriate diagnostic.
var bodyArgumentExpr = arguments[trailingClosureIndex].expression
bodyArgumentExpr = removeParentheses(from: bodyArgumentExpr) ?? bodyArgumentExpr
if let closureExpr = bodyArgumentExpr.as(ClosureExprSyntax.self),
let captureClause = closureExpr.signature?.capture,
!captureClause.items.isEmpty {
context.diagnose(.captureClauseUnsupported(captureClause, in: closureExpr, inExitTest: macro))
}

// TODO: use UUID() here if we can link to Foundation
let exitTestID = (UInt64.random(in: 0 ... .max), UInt64.random(in: 0 ... .max))
Expand Down
48 changes: 48 additions & 0 deletions Sources/TestingMacros/Support/DiagnosticMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -764,3 +764,51 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage {
var severity: DiagnosticSeverity
var fixIts: [FixIt] = []
}

// MARK: - Captured values

extension DiagnosticMessage {
/// Create a diagnostic message stating that a capture clause cannot be used
/// in an exit test.
///
/// - Parameters:
/// - captureClause: The invalid capture clause.
/// - closure: The closure containing `captureClause`.
/// - exitTestMacro: The containing exit test macro invocation.
///
/// - Returns: A diagnostic message.
static func captureClauseUnsupported(_ captureClause: ClosureCaptureClauseSyntax, in closure: ClosureExprSyntax, inExitTest exitTestMacro: some FreestandingMacroExpansionSyntax) -> Self {
let changes: [FixIt.Change]
if let signature = closure.signature,
Array(signature.with(\.capture, nil).tokens(viewMode: .sourceAccurate)).count == 1 {
// The only remaining token in the signature is `in`, so remove the whole
// signature tree instead of just the capture clause.
changes = [
.replaceTrailingTrivia(token: closure.leftBrace, newTrivia: ""),
.replace(
oldNode: Syntax(signature),
newNode: Syntax("" as ExprSyntax)
)
]
} else {
changes = [
.replace(
oldNode: Syntax(captureClause),
newNode: Syntax("" as ExprSyntax)
)
]
}

return Self(
syntax: Syntax(captureClause),
message: "Cannot specify a capture clause in closure passed to \(_macroName(exitTestMacro))",
severity: .error,
fixIts: [
FixIt(
message: MacroExpansionFixItMessage("Remove '\(captureClause.trimmed)'"),
changes: changes
),
]
)
}
}
17 changes: 17 additions & 0 deletions Tests/TestingMacrosTests/ConditionMacroTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,23 @@ struct ConditionMacroTests {
#expect(diagnostic.message.contains("is redundant"))
}

@Test(
"Capture list on an exit test produces a diagnostic",
arguments: [
"#expectExitTest(exitsWith: x) { [a] in }":
"Cannot specify a capture clause in closure passed to '#expectExitTest(exitsWith:_:)'"
]
)
func exitTestCaptureListProducesDiagnostic(input: String, expectedMessage: String) throws {
let (_, diagnostics) = try parse(input)

#expect(diagnostics.count > 0)
for diagnostic in diagnostics {
#expect(diagnostic.diagMessage.severity == .error)
#expect(diagnostic.message == expectedMessage)
}
}

@Test("Macro expansion is performed within a test function")
func macroExpansionInTestFunction() throws {
let input = ##"""
Expand Down