diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index 326522858..e21938041 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -157,8 +157,17 @@ extension ConditionMacro { expandedFunctionName = conditionArgument.expandedFunctionName } - // Capture any comments as well (either in source or as a macro argument.) + // Capture any comments as well -- either in source, preceding the + // expression macro or one of its lexical context nodes, or as an argument + // to the macro. let commentsArrayExpr = ArrayExprSyntax { + // Lexical context is ordered innermost-to-outermost, so reverse it to + // maintain the expected order. + for lexicalSyntaxNode in context.lexicalContext.trailingEffectExpressions.reversed() { + for commentTraitExpr in createCommentTraitExprs(for: lexicalSyntaxNode) { + ArrayElementSyntax(expression: commentTraitExpr) + } + } for commentTraitExpr in createCommentTraitExprs(for: macro) { ArrayElementSyntax(expression: commentTraitExpr) } diff --git a/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift b/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift index f67ca40ee..819b5fce3 100644 --- a/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift +++ b/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift @@ -12,7 +12,7 @@ import SwiftSyntax import SwiftSyntaxBuilder import SwiftSyntaxMacros -// MARK: - Finding effect keywords +// MARK: - Finding effect keywords and expressions /// A syntax visitor class that looks for effectful keywords in a given /// expression. @@ -69,6 +69,21 @@ func findEffectKeywords(in node: some SyntaxProtocol, context: some MacroExpansi return effectFinder.effectKeywords } +extension BidirectionalCollection { + /// The suffix of syntax nodes in this collection which are effectful + /// expressions, such as those for `try` or `await`. + var trailingEffectExpressions: some Collection { + reversed() + .prefix { node in + // This could be simplified if/when swift-syntax introduces a protocol + // which all effectful expression syntax node types conform to. + // See https://github.com/swiftlang/swift-syntax/issues/3040 + node.is(TryExprSyntax.self) || node.is(AwaitExprSyntax.self) || node.is(UnsafeExprSyntax.self) + } + .reversed() + } +} + // MARK: - Inserting effect keywords/thunks /// Make a function call expression to an effectful thunk function provided by diff --git a/Tests/TestingMacrosTests/ConditionMacroTests.swift b/Tests/TestingMacrosTests/ConditionMacroTests.swift index cd1333941..67531dabf 100644 --- a/Tests/TestingMacrosTests/ConditionMacroTests.swift +++ b/Tests/TestingMacrosTests/ConditionMacroTests.swift @@ -240,6 +240,59 @@ struct ConditionMacroTests { // Capture me Testing.__checkValue(try x(), expression: .__fromSyntaxNode("try x()"), comments: [.__line("// Capture me")], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected() """, + + """ + // Capture me + try #expect(x) + """: + """ + // Capture me + try Testing.__checkValue(x, expression: .__fromSyntaxNode("x"), comments: [.__line("// Capture me")], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected() + """, + + """ + // Capture me + await #expect(x) + """: + """ + // Capture me + await Testing.__checkValue(x, expression: .__fromSyntaxNode("x"), comments: [.__line("// Capture me")], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected() + """, + + """ + // Ignore me + + // Comment for try + try + // Comment for await + await + // Comment for expect + #expect(x) + """: + """ + // Comment for try + try + // Comment for await + await + // Comment for expect + Testing.__checkValue(x, expression: .__fromSyntaxNode("x"), comments: [.__line("// Comment for try"), .__line("// Comment for await"), .__line("// Comment for expect")], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected() + """, + + """ + // Ignore me + func example() { + // Capture me + #expect(x()) + } + """: + """ + func example() { + // Capture me + Testing.__checkFunctionCall((), calling: { _ in + x() + }, expression: .__fromFunctionCall(nil, "x"), comments: [.__line("// Capture me")], isRequired: false, sourceLocation: Testing.SourceLocation.__here()).__expected() + } + """, ] ) func commentCapture(input: String, expectedOutput: String) throws {