From 5cd63a83c765a40585a58b1e3d4adec1d30b396d Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 23 Apr 2025 08:11:46 -0500 Subject: [PATCH 1/3] Include code comments before expectations which are preceded by try/await in recorded issues --- Sources/TestingMacros/ConditionMacro.swift | 11 +++++- .../ConditionMacroTests.swift | 37 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index 326522858..0d973b1ee 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.reversed() { + for commentTraitExpr in createCommentTraitExprs(for: lexicalSyntaxNode) { + ArrayElementSyntax(expression: commentTraitExpr) + } + } for commentTraitExpr in createCommentTraitExprs(for: macro) { ArrayElementSyntax(expression: commentTraitExpr) } diff --git a/Tests/TestingMacrosTests/ConditionMacroTests.swift b/Tests/TestingMacrosTests/ConditionMacroTests.swift index cd1333941..4e9975108 100644 --- a/Tests/TestingMacrosTests/ConditionMacroTests.swift +++ b/Tests/TestingMacrosTests/ConditionMacroTests.swift @@ -240,6 +240,43 @@ 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() + """, ] ) func commentCapture(input: String, expectedOutput: String) throws { From 47951ff27484ce862c089d4fd4671f846099a8aa Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 23 Apr 2025 21:19:27 -0500 Subject: [PATCH 2/3] Filter lexical context to only effectful expr nodes --- Sources/TestingMacros/ConditionMacro.swift | 2 +- .../Support/EffectfulExpressionHandling.swift | 15 ++++++++++++++- .../TestingMacrosTests/ConditionMacroTests.swift | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index 0d973b1ee..ee01ef813 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -163,7 +163,7 @@ extension ConditionMacro { let commentsArrayExpr = ArrayExprSyntax { // Lexical context is ordered innermost-to-outermost, so reverse it to // maintain the expected order. - for lexicalSyntaxNode in context.lexicalContext.reversed() { + for lexicalSyntaxNode in context.lexicalContext.effectExpressions.reversed() { for commentTraitExpr in createCommentTraitExprs(for: lexicalSyntaxNode) { ArrayElementSyntax(expression: commentTraitExpr) } diff --git a/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift b/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift index f67ca40ee..6c7182b41 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,19 @@ func findEffectKeywords(in node: some SyntaxProtocol, context: some MacroExpansi return effectFinder.effectKeywords } +extension Sequence { + /// The syntax nodes in this sequence which are effectful expressions, such as + /// those for `try` or `await`. + var effectExpressions: some Sequence { + filter { node in + // This could be made simpler 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) + } + } +} + // 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 4e9975108..67531dabf 100644 --- a/Tests/TestingMacrosTests/ConditionMacroTests.swift +++ b/Tests/TestingMacrosTests/ConditionMacroTests.swift @@ -277,6 +277,22 @@ struct ConditionMacroTests { // 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 { From e18f26cba9ebd884d0357376ae41d723b3f32ba6 Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 23 Apr 2025 22:51:12 -0500 Subject: [PATCH 3/3] Limit lexical context to the matching suffix nodes --- Sources/TestingMacros/ConditionMacro.swift | 2 +- .../Support/EffectfulExpressionHandling.swift | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Sources/TestingMacros/ConditionMacro.swift b/Sources/TestingMacros/ConditionMacro.swift index ee01ef813..e21938041 100644 --- a/Sources/TestingMacros/ConditionMacro.swift +++ b/Sources/TestingMacros/ConditionMacro.swift @@ -163,7 +163,7 @@ extension ConditionMacro { let commentsArrayExpr = ArrayExprSyntax { // Lexical context is ordered innermost-to-outermost, so reverse it to // maintain the expected order. - for lexicalSyntaxNode in context.lexicalContext.effectExpressions.reversed() { + for lexicalSyntaxNode in context.lexicalContext.trailingEffectExpressions.reversed() { for commentTraitExpr in createCommentTraitExprs(for: lexicalSyntaxNode) { ArrayElementSyntax(expression: commentTraitExpr) } diff --git a/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift b/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift index 6c7182b41..819b5fce3 100644 --- a/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift +++ b/Sources/TestingMacros/Support/EffectfulExpressionHandling.swift @@ -69,16 +69,18 @@ func findEffectKeywords(in node: some SyntaxProtocol, context: some MacroExpansi return effectFinder.effectKeywords } -extension Sequence { - /// The syntax nodes in this sequence which are effectful expressions, such as - /// those for `try` or `await`. - var effectExpressions: some Sequence { - filter { node in - // This could be made simpler 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) - } +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() } }