Skip to content

Commit 404fce6

Browse files
committed
Add fixed source to assertMacroExpansion
1 parent 2c847f8 commit 404fce6

File tree

3 files changed

+103
-52
lines changed

3 files changed

+103
-52
lines changed

Sources/SwiftSyntaxMacrosTestSupport/Assertions.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,18 @@ 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: 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.
258+
/// - fixedSource: If specified, asserts that the source code after applying Fix-Its matches this string.
257259
/// - testModuleName: The name of the test module to use.
258260
/// - testFileName: The name of the test file name to use.
261+
/// - indentationWidth: The indentation width used in the expansion.
259262
public func assertMacroExpansion(
260263
_ originalSource: String,
261264
expandedSource expectedExpandedSource: String,
262265
diagnostics: [DiagnosticSpec] = [],
263266
macros: [String: Macro.Type],
267+
applyFixIts: [String]? = nil,
268+
fixedSource expectedFixedSource: String? = nil,
264269
testModuleName: String = "TestModule",
265270
testFileName: String = "test.swift",
266271
indentationWidth: Trivia = .spaces(4),
@@ -317,4 +322,16 @@ public func assertMacroExpansion(
317322
assertDiagnostic(actualDiag, in: context, expected: expectedDiag)
318323
}
319324
}
325+
326+
// Applying Fix-Its
327+
if let expectedFixedSource = expectedFixedSource {
328+
let fixedTree = FixItApplier.applyFixes(in: context.diagnostics, withMessages: applyFixIts, to: origSourceFile)
329+
let fixedTreeDescription = fixedTree.description
330+
assertStringsEqualWithDiff(
331+
fixedTreeDescription.trimmingTrailingWhitespace(),
332+
expectedFixedSource.trimmingTrailingWhitespace(),
333+
file: file,
334+
line: line
335+
)
336+
}
320337
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
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+
fileprivate struct Edit {
18+
let startUtf8Offset: Int
19+
let endUtf8Offset: Int
20+
let replacement: String
21+
}
22+
23+
var changes: [FixIt.Change]
24+
25+
private var locationConverter: SourceLocationConverter?
26+
private var edits: [Edit] = []
27+
28+
init(diagnostics: [Diagnostic], withMessages messages: [String]?) {
29+
let messages = messages ?? diagnostics.compactMap { $0.fixIts.first?.message.message }
30+
31+
self.changes =
32+
diagnostics
33+
.flatMap { $0.fixIts }
34+
.filter {
35+
return messages.contains($0.message.message)
36+
}
37+
.flatMap { $0.changes }
38+
39+
super.init(viewMode: .all)
40+
}
41+
42+
public override func visitPre(_ node: Syntax) {
43+
if locationConverter == nil && !node.hasParent {
44+
locationConverter = SourceLocationConverter(fileName: "", tree: node)
45+
46+
for change in self.changes {
47+
switch change {
48+
case .replace(oldNode: let oldNode, newNode: let newNode):
49+
let oldStartLocation = oldNode.startLocation(converter: locationConverter!)
50+
let oldEndLocation = oldNode.startLocation(converter: locationConverter!)
51+
52+
edits.append(Edit(startUtf8Offset: oldStartLocation.offset, endUtf8Offset: oldEndLocation.offset, replacement: newNode.description))
53+
54+
default:
55+
break
56+
}
57+
}
58+
}
59+
}
60+
61+
public override func visitAny(_ node: Syntax) -> Syntax? {
62+
return nil
63+
}
64+
65+
public override func visit(_ node: TokenSyntax) -> TokenSyntax {
66+
var modifiedNode = node
67+
for change in changes {
68+
switch change {
69+
case .replaceLeadingTrivia(token: let changedNode, newTrivia: let newTrivia) where changedNode.id == node.id:
70+
modifiedNode = node.with(\.leadingTrivia, newTrivia)
71+
case .replaceTrailingTrivia(token: let changedNode, newTrivia: let newTrivia) where changedNode.id == node.id:
72+
modifiedNode = node.with(\.trailingTrivia, newTrivia)
73+
default:
74+
break
75+
}
76+
}
77+
return modifiedNode
78+
}
79+
80+
/// If `messages` is `nil`, applies all Fix-Its in `diagnostics` to `tree` and returns the fixed syntax tree.
81+
/// If `messages` is not `nil`, applies only Fix-Its whose message is in `messages`.
82+
public static func applyFixes<T: SyntaxProtocol>(in diagnostics: [Diagnostic], withMessages messages: [String]?, to tree: T) -> Syntax {
83+
let applier = FixItApplier(diagnostics: diagnostics, withMessages: messages)
84+
return applier.rewrite(tree)
85+
}
86+
}

Tests/SwiftParserTest/Assertions.swift

Lines changed: 0 additions & 52 deletions
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,

0 commit comments

Comments
 (0)