Skip to content

Commit 6f3c6e7

Browse files
committed
Enhance assertMacroExpansion to Validate Post-Fix-It Macro Expansions
This commit introduces an enhancement to the `assertMacroExpansion` function in SwiftSyntax's testing suite. The key addition is the capability to validate macro expansions on source code after the application of Fix-Its. This change ensures a more robust and comprehensive testing process, particularly for macros that propose fix-its for diagnostics. Modifications include: - Addition of a new parameter `expandedFixedSource` in test assertions. This parameter allows users to assert against the expanded source code after applying Fix-Its. - Update of `assertMacroExpansion` to handle the new testing workflow. It now performs macro expansion tests on both the original and the fixed source code, ensuring that both the pre-fix and post-fix states are correctly validated. - Refinement of the internal logic for applying Fix-Its and conducting macro expansion tests, ensuring accuracy and efficiency in the testing process. - Update of test cases in `AddBlockerTests.swift` and `AddCompletionHandlerMacroTests.swift` to demonstrate and validate the new functionality. Resolves: #2333
1 parent ae42a2e commit 6f3c6e7

File tree

3 files changed

+109
-41
lines changed

3 files changed

+109
-41
lines changed

Examples/Tests/MacroExamples/Implementation/Expression/AddBlockerTests.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,12 @@ final class AddBlockerTests: XCTestCase {
5858
],
5959
macros: macros,
6060
applyFixIts: ["use '-'"],
61-
fixedSource:
62-
"""
61+
fixedSource: """
6362
#addBlocker(x * y - z)
6463
""",
64+
expandedFixedSource: """
65+
x * y - z
66+
""",
6567
indentationWidth: .spaces(2)
6668
)
6769
}

Examples/Tests/MacroExamples/Implementation/Peer/AddCompletionHandlerMacroTests.swift

+13
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ final class AddCompletionHandlerMacroTests: XCTestCase {
135135
}
136136
}
137137
""",
138+
expandedFixedSource: """
139+
struct Test {
140+
func fetchData() async -> String {
141+
return "Hello, World!"
142+
}
143+
144+
func fetchData(completionHandler: @escaping (String) -> Void) {
145+
Task {
146+
completionHandler(await fetchData())
147+
}
148+
}
149+
}
150+
""",
138151
indentationWidth: .spaces(2)
139152
)
140153
}

Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift

+92-39
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ func assertDiagnostic(
256256
/// (e.g., `StringifyMacro.self`).
257257
/// - applyFixIts: If specified, filters the Fix-Its that are applied to generate `fixedSource` to only those whose message occurs in this array. If `nil`, all Fix-Its from the diagnostics are applied.
258258
/// - fixedSource: If specified, asserts that the source code after applying Fix-Its matches this string.
259+
/// - expectedExpandedFixedSource: If specified, asserts against the expanded fixed source.
259260
/// - testModuleName: The name of the test module to use.
260261
/// - testFileName: The name of the test file name to use.
261262
/// - indentationWidth: The indentation width used in the expansion.
@@ -266,48 +267,26 @@ public func assertMacroExpansion(
266267
macros: [String: Macro.Type],
267268
applyFixIts: [String]? = nil,
268269
fixedSource expectedFixedSource: String? = nil,
270+
expandedFixedSource expectedExpandedFixedSource: String? = nil,
269271
testModuleName: String = "TestModule",
270272
testFileName: String = "test.swift",
271273
indentationWidth: Trivia = .spaces(4),
272274
file: StaticString = #file,
273275
line: UInt = #line
274276
) {
275-
// Parse the original source file.
276-
let origSourceFile = Parser.parse(source: originalSource)
277-
278-
// Expand all macros in the source.
279-
let context = BasicMacroExpansionContext(
280-
sourceFiles: [origSourceFile: .init(moduleName: testModuleName, fullFilePath: testFileName)]
281-
)
282-
283-
let expandedSourceFile = origSourceFile.expand(macros: macros, in: context, indentationWidth: indentationWidth)
284-
let diags = ParseDiagnosticsGenerator.diagnostics(for: expandedSourceFile)
285-
if !diags.isEmpty {
286-
XCTFail(
287-
"""
288-
Expanded source should not contain any syntax errors, but contains:
289-
\(DiagnosticsFormatter.annotatedSource(tree: expandedSourceFile, diags: diags))
290-
291-
Expanded syntax tree was:
292-
\(expandedSourceFile.debugDescription)
293-
""",
294-
file: file,
295-
line: line
296-
)
297-
}
298-
299-
assertStringsEqualWithDiff(
300-
expandedSourceFile.description.trimmingCharacters(in: .newlines),
301-
expectedExpandedSource.trimmingCharacters(in: .newlines),
302-
"Macro expansion did not produce the expected expanded source",
303-
additionalInfo: """
304-
Actual expanded source:
305-
\(expandedSourceFile)
306-
""",
277+
// Performs the macro expansion test and retrieves the original source file and context.
278+
let (originalSourceFile, context) = performMacroExpansionTest(
279+
originalSource,
280+
expandedSource: expectedExpandedSource,
281+
macros: macros,
282+
testModuleName: testModuleName,
283+
testFileName: testFileName,
284+
indentationWidth: indentationWidth,
307285
file: file,
308286
line: line
309287
)
310288

289+
// Asserts that the number of diagnostics produced matches the expected count.
311290
if context.diagnostics.count != diagnostics.count {
312291
XCTFail(
313292
"""
@@ -318,31 +297,105 @@ public func assertMacroExpansion(
318297
line: line
319298
)
320299
} else {
300+
// Compares each actual diagnostic with its expected counterpart.
321301
for (actualDiag, expectedDiag) in zip(context.diagnostics, diagnostics) {
322302
assertDiagnostic(actualDiag, in: context, expected: expectedDiag)
323303
}
324304
}
325305

326-
// Applying Fix-Its
327-
if let expectedFixedSource = expectedFixedSource {
306+
// Applies Fix-Its if necessary, based on the provided expected arguments.
307+
if expectedFixedSource != nil || expectedExpandedFixedSource != nil {
328308
let messages = applyFixIts ?? context.diagnostics.compactMap { $0.fixIts.first?.message.message }
329309

310+
// Collects edits from diagnostics and applies them to the original source file.
330311
let edits =
331312
context.diagnostics
332313
.flatMap(\.fixIts)
333314
.filter { messages.contains($0.message.message) }
334315
.flatMap { $0.changes }
335316
.map { $0.edit(in: context) }
336317

337-
let fixedTree = FixItApplier.apply(edits: edits, to: origSourceFile)
338-
let fixedTreeDescription = fixedTree.description
339-
assertStringsEqualWithDiff(
340-
fixedTreeDescription.trimmingTrailingWhitespace(),
341-
expectedFixedSource.trimmingTrailingWhitespace(),
318+
let fixedTree = FixItApplier.apply(edits: edits, to: originalSourceFile)
319+
let fixedTreeDescription = fixedTree.description.trimmingTrailingWhitespace()
320+
321+
// Asserts that the fixed source matches the expected fixed source.
322+
if let expectedFixedSource {
323+
assertStringsEqualWithDiff(
324+
fixedTreeDescription,
325+
expectedFixedSource.trimmingTrailingWhitespace(),
326+
file: file,
327+
line: line
328+
)
329+
}
330+
331+
// Performs macro expansion test on the fixed source if an expected expanded fixed source is provided.
332+
if let expectedExpandedFixedSource {
333+
performMacroExpansionTest(
334+
fixedTreeDescription,
335+
expandedSource: expectedExpandedFixedSource.trimmingTrailingWhitespace(),
336+
macros: macros,
337+
testModuleName: testModuleName,
338+
testFileName: testFileName,
339+
indentationWidth: indentationWidth,
340+
file: file,
341+
line: line
342+
)
343+
}
344+
}
345+
}
346+
347+
@discardableResult
348+
private func performMacroExpansionTest(
349+
_ originalSource: String,
350+
expandedSource expectedExpandedSource: String,
351+
macros: [String: Macro.Type],
352+
testModuleName: String,
353+
testFileName: String,
354+
indentationWidth: Trivia,
355+
file: StaticString,
356+
line: UInt
357+
) -> (originalSourceFile: SourceFileSyntax, context: BasicMacroExpansionContext) {
358+
// Parses the original source file from the provided string.
359+
let originalSourceFile = Parser.parse(source: originalSource)
360+
361+
// Creates a context for macro expansion with the original source file.
362+
let context = BasicMacroExpansionContext(
363+
sourceFiles: [originalSourceFile: .init(moduleName: testModuleName, fullFilePath: testFileName)]
364+
)
365+
366+
// Performs macro expansion on the original source file.
367+
let expandedSourceFile = originalSourceFile.expand(macros: macros, in: context, indentationWidth: indentationWidth)
368+
369+
// Generates and checks for any syntax errors in the expanded source.
370+
let diags = ParseDiagnosticsGenerator.diagnostics(for: expandedSourceFile)
371+
if !diags.isEmpty {
372+
XCTFail(
373+
"""
374+
Expanded source should not contain any syntax errors, but contains:
375+
\(DiagnosticsFormatter.annotatedSource(tree: expandedSourceFile, diags: diags))
376+
377+
Expanded syntax tree was:
378+
\(expandedSourceFile.debugDescription)
379+
""",
342380
file: file,
343381
line: line
344382
)
345383
}
384+
385+
// Asserts that the expanded source matches the expected expanded source.
386+
assertStringsEqualWithDiff(
387+
expandedSourceFile.description.trimmingCharacters(in: .newlines),
388+
expectedExpandedSource.trimmingCharacters(in: .newlines),
389+
"Macro expansion did not produce the expected expanded source",
390+
additionalInfo: """
391+
Actual expanded source:
392+
\(expandedSourceFile)
393+
""",
394+
file: file,
395+
line: line
396+
)
397+
398+
return (originalSourceFile, context)
346399
}
347400

348401
fileprivate extension FixIt.Change {

0 commit comments

Comments
 (0)