Skip to content

Commit 905c5c5

Browse files
committed
Add fixed source to assertMacroExpansion
1 parent e340af4 commit 905c5c5

File tree

4 files changed

+119
-68
lines changed

4 files changed

+119
-68
lines changed

Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift

+26
Original file line numberDiff line numberDiff line change
@@ -254,16 +254,24 @@ func assertDiagnostic(
254254
/// - macros: The macros that should be expanded, provided as a dictionary
255255
/// mapping macro names (e.g., `"stringify"`) to implementation types
256256
/// (e.g., `StringifyMacro.self`).
257+
/// - applyFixIts: Applies only the fix-its with these messages.
258+
/// Ohterwise all Fix-Its from diagnostics will be applied.
259+
/// - fixedSource: Asserts that the source after applying fix-its matches
257260
/// - testModuleName: The name of the test module to use.
258261
/// - testFileName: The name of the test file name to use.
262+
/// - indentationWidth: The indentation width used in the expansion.
263+
/// - options: Options for asserting fixed source.
259264
public func assertMacroExpansion(
260265
_ originalSource: String,
261266
expandedSource expectedExpandedSource: String,
262267
diagnostics: [DiagnosticSpec] = [],
263268
macros: [String: Macro.Type],
269+
applyFixIts: [String]? = nil,
270+
fixedSource expectedFixedSource: String? = nil,
264271
testModuleName: String = "TestModule",
265272
testFileName: String = "test.swift",
266273
indentationWidth: Trivia = .spaces(4),
274+
options: AssertParseOptions = [],
267275
file: StaticString = #file,
268276
line: UInt = #line
269277
) {
@@ -316,4 +324,22 @@ public func assertMacroExpansion(
316324
assertDiagnostic(actualDiag, in: context, expected: expectedDiag)
317325
}
318326
}
327+
328+
// Applying Fix-Its
329+
if let expectedFixedSource = expectedFixedSource {
330+
let fixedTree = FixItApplier.applyFixes(in: diags, withMessages: applyFixIts, to: expandedSourceFile)
331+
var fixedTreeDescription = fixedTree.description
332+
if options.contains(.normalizeNewlinesInFixedSource) {
333+
fixedTreeDescription =
334+
fixedTreeDescription
335+
.replacingOccurrences(of: "\r\n", with: "\n")
336+
.replacingOccurrences(of: "\r", with: "\n")
337+
}
338+
assertStringsEqualWithDiff(
339+
fixedTreeDescription.trimmingTrailingWhitespace(),
340+
expectedFixedSource.trimmingTrailingWhitespace(),
341+
file: file,
342+
line: line
343+
)
344+
}
319345
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
public struct AssertParseOptions: OptionSet {
14+
public var rawValue: UInt8
15+
16+
public init(rawValue: UInt8) {
17+
self.rawValue = rawValue
18+
}
19+
20+
/// Trivia mismatches in the substructure should cause failures
21+
public static let substructureCheckTrivia = AssertParseOptions(rawValue: 1 << 0)
22+
23+
/// Replace all `\r` and `\r\n` in the fixed source by `\n`.
24+
/// Useful to match source code that contains other line endings to expected
25+
/// fixed source that has `\n` line endings.
26+
public static let normalizeNewlinesInFixedSource = AssertParseOptions(rawValue: 1 << 1)
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftSyntax
14+
import SwiftDiagnostics
15+
16+
public class FixItApplier: SyntaxRewriter {
17+
var changes: [FixIt.Change]
18+
19+
init(diagnostics: [Diagnostic], withMessages messages: [String]?) {
20+
let messages = messages ?? diagnostics.compactMap { $0.fixIts.first?.message.message }
21+
22+
self.changes =
23+
diagnostics
24+
.flatMap { $0.fixIts }
25+
.filter {
26+
return messages.contains($0.message.message)
27+
}
28+
.flatMap { $0.changes }
29+
30+
super.init(viewMode: .all)
31+
}
32+
33+
public override func visitAny(_ node: Syntax) -> Syntax? {
34+
for change in changes {
35+
switch change {
36+
case .replace(oldNode: let oldNode, newNode: let newNode) where oldNode.id == node.id:
37+
return newNode
38+
default:
39+
break
40+
}
41+
}
42+
return nil
43+
}
44+
45+
public override func visit(_ node: TokenSyntax) -> TokenSyntax {
46+
var modifiedNode = node
47+
for change in changes {
48+
switch change {
49+
case .replaceLeadingTrivia(token: let changedNode, newTrivia: let newTrivia) where changedNode.id == node.id:
50+
modifiedNode = node.with(\.leadingTrivia, newTrivia)
51+
case .replaceTrailingTrivia(token: let changedNode, newTrivia: let newTrivia) where changedNode.id == node.id:
52+
modifiedNode = node.with(\.trailingTrivia, newTrivia)
53+
default:
54+
break
55+
}
56+
}
57+
return modifiedNode
58+
}
59+
60+
/// If `messages` is `nil`, applies all Fix-Its in `diagnostics` to `tree` and returns the fixed syntax tree.
61+
/// If `messages` is not `nil`, applies only Fix-Its whose message is in `messages`.
62+
public static func applyFixes<T: SyntaxProtocol>(in diagnostics: [Diagnostic], withMessages messages: [String]?, to tree: T) -> Syntax {
63+
let applier = FixItApplier(diagnostics: diagnostics, withMessages: messages)
64+
return applier.rewrite(tree)
65+
}
66+
}

Tests/SwiftParserTest/Assertions.swift

-68
Original file line numberDiff line numberDiff line change
@@ -276,58 +276,6 @@ struct DiagnosticSpec {
276276
}
277277
}
278278

279-
class FixItApplier: SyntaxRewriter {
280-
var changes: [FixIt.Change]
281-
282-
init(diagnostics: [Diagnostic], withMessages messages: [String]?) {
283-
let messages = messages ?? diagnostics.compactMap { $0.fixIts.first?.message.message }
284-
285-
self.changes =
286-
diagnostics
287-
.flatMap { $0.fixIts }
288-
.filter {
289-
return messages.contains($0.message.message)
290-
}
291-
.flatMap { $0.changes }
292-
293-
super.init(viewMode: .all)
294-
}
295-
296-
public override func visitAny(_ node: Syntax) -> Syntax? {
297-
for change in changes {
298-
switch change {
299-
case .replace(oldNode: let oldNode, newNode: let newNode) where oldNode.id == node.id:
300-
return newNode
301-
default:
302-
break
303-
}
304-
}
305-
return nil
306-
}
307-
308-
override func visit(_ node: TokenSyntax) -> TokenSyntax {
309-
var modifiedNode = node
310-
for change in changes {
311-
switch change {
312-
case .replaceLeadingTrivia(token: let changedNode, newTrivia: let newTrivia) where changedNode.id == node.id:
313-
modifiedNode = node.with(\.leadingTrivia, newTrivia)
314-
case .replaceTrailingTrivia(token: let changedNode, newTrivia: let newTrivia) where changedNode.id == node.id:
315-
modifiedNode = node.with(\.trailingTrivia, newTrivia)
316-
default:
317-
break
318-
}
319-
}
320-
return modifiedNode
321-
}
322-
323-
/// If `messages` is `nil`, applies all Fix-Its in `diagnostics` to `tree` and returns the fixed syntax tree.
324-
/// If `messages` is not `nil`, applies only Fix-Its whose message is in `messages`.
325-
public static func applyFixes<T: SyntaxProtocol>(in diagnostics: [Diagnostic], withMessages messages: [String]?, to tree: T) -> Syntax {
326-
let applier = FixItApplier(diagnostics: diagnostics, withMessages: messages)
327-
return applier.rewrite(tree)
328-
}
329-
}
330-
331279
/// Assert that `location` is the same as that of `locationMarker` in `tree`.
332280
func assertLocation<T: SyntaxProtocol>(
333281
_ location: SourceLocation,
@@ -511,22 +459,6 @@ class TokenPresenceFlipper: SyntaxRewriter {
511459
}
512460
}
513461

514-
public struct AssertParseOptions: OptionSet {
515-
public var rawValue: UInt8
516-
517-
public init(rawValue: UInt8) {
518-
self.rawValue = rawValue
519-
}
520-
521-
/// Trivia mismatches in the substructure should cause failures
522-
public static let substructureCheckTrivia = AssertParseOptions(rawValue: 1 << 0)
523-
524-
/// Replace all `\r` and `\r\n` in the fixed source by `\n`.
525-
/// Useful to match source code that contains other line endings to expected
526-
/// fixed source that has `\n` line endings.
527-
public static let normalizeNewlinesInFixedSource = AssertParseOptions(rawValue: 1 << 1)
528-
}
529-
530462
/// Same as `assertParse` overload with a `(String) -> S` `parse`,
531463
/// parsing the resulting `String` as a ``SourceFileSyntax``.
532464
func assertParse(

0 commit comments

Comments
 (0)